Click here to Skip to main content
Click here to Skip to main content

Indexed Dictionary

, 4 Jul 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
An article on a generic collection accessible both as a dictionary and as an indexed list.

Introduction

This code provides a generic collection of key-value pairs where the values can also be accessed by index. The collection also exposes events occurring when items are added or removed.

Background

I came across the need for such a collection developing an infrastructure based on collections of uniquely identified objects (the application contains forms, the forms contains items etc.). The collections had to be accessible and iterateable with and without unique ID's. Searching the web I came across a collection by Brad Vincent which can be found in a post in his blog here. I made some fixes and extensions and the result is presented here.

Using the Code

Using the Dictionary

The IndexedDictionary class is initiated with two types, the first of which is the key type and the second is the value type:

    
IndexedDictionary<string, int> dic = new IndexedDictionary<string, int>()

Values may be added using the Add function:

    dic.Add("key1", 10);
dic.Add("key2", 20);

Or using the AddAt function which will insert the value at a specific location:

    dic.AddAt(1, "key1.5", 15);

The Remove function removes an item by key:

    dic.Remove("key1.5");

While the RemoveAt function removes by index:

    dic.RemoveAt(0);

The collection may be accessed by key:

    string s1 = dic["key2"];

Or by index:

    string s2 = dic[0];

The collection can be iterated using foreach loops on the keys collection or on the values collection:

// Iterate by key:
foreach (string key in dic.Keys)
{
    sum += dic[key];
}

// Iterate by value:
foreach (string value in dic.Values)
{
    sum += value;
}

It can also be iterated with a for loop using the index (something you cannot do with a normal dictionary):

// Iterate by index:
for (int i = 0; i < dic.Count; i++)
{
    sum += dic[i];
}

You may also get the key in a specified index:

    string key = dic.GetKeyByIndex(1);

You may also use a sutiable predicate to get the index of a given key:

    int index = dic.GetIndex(s => s == KEY_STRING + testParam);

The collection cannot be initiated with a key of type int, because there would be no definitive answer to the question how dic[i] should pick it's value — by key or by index (this is probably why you don't get this kind of collection from Microsoft, not clean enough...). Trying to do so will yield an exception.

Using the Events

I found the collections I used heavily lacking events occurring when items are added and removed and for when thy are cleared. This functionality opens a great big world for the programmer, especially when working with dictated collection (dictated by an infrastructure, for instance). The IndexedDictionary class exposes six such events, two for each of the following: Add, Remove and Clear. The events occur before and after each of the actions (and are so named: BeforeAdd, AfterAdd etc.)

To use the events just hook up to them. The functions and the event arguments are generic (using the dictionary's types) so it is best to let the visual studio create the method stub for you:

dic.BeforeAdd += new DictionaryBeforeDelegate<string, int>(dic_BeforeAdd);

static void dic_BeforeAdd(object sender, DictionaryBeforeEventArgs<string, int> e)
{
    throw new Exception("The method or operation is not implemented.");
}

Included in the event argument you would find the key and the value of the item in question (except for the clear events, of course). These can be manipulated if necessary. You can also get the collection itself by casting the sender paremater:

static void dic_BeforeAdd(object sender, DictionaryBeforeEventArgs<string, int> e)
{
    string key = e.Key;
    e.Value = "Cahnged value";
    int i = ((IndexedDictionary<string, int>)sender).Count;
}

When handling a before event (such as BeforeAdd), the EventArgs include a boolean property named Bubble. If this property is set to false the action is cancelled (this is an older style version of the e.Cancel Microsoft is using today).

static void dic_BeforeAdd(object sender, DictionaryBeforeEventArgs<string, int> e)
{
    // Cancel the add operation:
    e.Bubble = false;
}

Other Features

ThrowErrorOnInvalidRemove Property

The IndexedDictionary contains an immutable property (set only in the constructor) named ThrowErrorOnInvalidRemove. This property determines whether or not the collection will throw an exception when a non-existing key is removed.

ReplaceDuplicateKeys

The IndexedDictionary contains an immutable property (set only in the constructor) named ReplaceDuplicateKeys. This property determines whether or not the collection will throw an exception when an item is added with a key already used in the collection.

Points of Interest

The Inner Works

This collection is based on a dictionary and contains an inner list of the dictionary keys (named m_col) which provides it with its list qualities. The Add, Remove and Clear functions are hidden (using the new keyword) by new functions that take care of the list as well as delegating the work to the dictionary and raising the events. This way, all the goodies included in the MS generic dictionary are provided free of charge.

The Inner Classes

The IndexedDictionary class includes the definitions of the delegates and event argument classes used by the events. These definitions are all genreic and require the type to be appeded onto them during construction. This is required since the event args are supposed to carry the strongly typed key and value and they must match the types defined for the specific dictionary.

I find it amusing to write those '+=' and get the function with its generic definitions intact (I sometimes do it just for the fun of it).

The Other Options

The following is free advertisement for the competition (your alternatives):

System.Collections.Specialized.NameObjectCollectionBase is a dictionary base with a string only key and an object value that can be accessed by index. This style (and namespace) of collection was used in .NET 1.0, before generics came along.

It is abstract so to use it you would have to inherit it, and you would have an interesting time figuring out the way to access the values in the hash table hiding inside.

I, for once, think using a non-type-safe collection is unacceptable today.

System.Collection.ObjectModel.KeyedCollection is an abstract generic collection (generic both in key and value) accessible by index, with an interesting twist: It is also abstract and to use is you must provide a function that extracts the key from the item (TKey GetKeyForItem(TValue)).

This collection is in fact a list of items containing both the key and the value of a dictionary entry. This collection also does not support int as an index.

Rune Baess offers here, at CodeProject, an already implemented collection based on the System.Collection.ObjectModel.KeyedCollection, where you supply the function as a delegate during initialization, by implementing an interface on the items class, or by decorating a predesigned property on the items class with an attribute. Check it out.

Loose Ends

  • Add and Remove events should contain the index of the item being added or removed. Implementing that one turned out to be a bit of a pain so I left it out — feel free to take over.
  • The Microsoft dictionary supports adding values by assigning them to a new key:
        dic[newKey] = newValue;

    In the case of our IndexedDictionary, this should have also added the value into the list. For now, it just throws an exception.

  • Using some numeric types as keys might prove to be problematic. Consider the following coed:
        IndexedDictionary<double, string> dic = new IndexedDictionary<double, string>()
    dic.Add(1, "TEST1");
    dic.Add(0, "TEST0");

    Should dic[0] be "TEST1" or "TEST0"? This can be solved by further limiting the allowed key types (which is a bit crude), or someone else might have a better idea.

History

July 2009 -

  • Method "GetIndex" added at the request of Reimer van Elst.
  • Project converted to VSTO 2008.
  • Test Project added.

Janurary 2009 - Method "GetKeyByIndex" added at the request of bbender.

November 2008 - This is the first version of this article (and my first here). I would like to thank Brad Vincent again for his work, and for allowing me to post it here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Asher Barak
Software Developer Ziv systems, Israel
Israel Israel
Starting with Apple IIe BASICA, and working my way through Pascal, Power Builder, Visual basic (and the light office VBA) C, C++, I am now a C# .NET developer and designer (and a big fan of the .NET framework).
 
I am currently leading a major effort writing a new framework for SAP Business One extensions and overseeing the development of four products on the same (yet unfinished) framework

Comments and Discussions

 
GeneralApplicationException changes PinmemberDewey5-Jul-09 10:59 
GeneralRe: ApplicationException changes PinmemberAsher Barak5-Jul-09 20:44 
GeneralRe: ApplicationException changes PinmemberDewey8-Jul-09 1:04 
GeneralRe: ApplicationException changes PinmemberAsher Barak8-Jul-09 4:52 
GeneralTest Project PinmemberDewey5-Jul-09 10:39 
GeneralRe: Test Project PinmemberAsher Barak5-Jul-09 20:51 
GeneralGetIndexByKey PinmemberReimer van Elst26-Jun-09 21:48 
GeneralRe: GetIndexByKey PinmemberAsher Barak30-Jun-09 1:28 
GeneralIsn't this similar to LinkedList class PinmemberSyntaxC421-Jan-09 19:47 
AnswerRe: Isn't this similar to LinkedList class PinmemberAsher Barak21-Jan-09 21:37 
Generalignore case for string key PinmemberUnruled Boy20-Jan-09 19:49 
GeneralRe: ignore case for string key PinmemberPIEBALDconsult21-Jan-09 4:30 
AnswerRe: ignore case for string key PinmemberAsher Barak21-Jan-09 9:28 
GeneralI like it PinmemberUnruled Boy20-Jan-09 19:36 
GeneralRe: I like it PinmemberAsher Barak21-Jan-09 9:30 
GeneralOh, and PinmemberPIEBALDconsult20-Jan-09 14:06 
GeneralUh oh PinmemberPIEBALDconsult20-Jan-09 13:47 
AnswerRe: Uh oh PinmemberAsher Barak21-Jan-09 9:54 
GeneralAnother approach PinmemberAlexander Turlov11-Nov-08 11:29 
GeneralRe: Another approach PinmemberPaul B.11-Nov-08 12:06 
AnswerRe: Another approach PinmemberAsher Barak11-Nov-08 18:56 
GeneralRe: Another approach Pinmembermariahayek23-Nov-08 12:31 
GeneralRe: Another approach Pinmemberbbender15-Jan-09 7:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411023.1 | Last Updated 4 Jul 2009
Article Copyright 2008 by Asher Barak
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid