Click here to Skip to main content
Email Password   helpLost your password?

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

History

July 2009 -

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.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralApplicationException changes
Dewey
10:59 5 Jul '09  
It's probably wise to change your exception inheritance from

public class IndexedDictionaryException : ApplicationException
To

public class IndexedDictionaryException : Exception
This makes it compatible with Silverlight, and I believe ApplicationException is no longer recommended.
GeneralRe: ApplicationException changes
Asher Barak
20:44 5 Jul '09  
Dewey wrote:
This makes it compatible with Silverlight, and I believe ApplicationException is no longer recommended.

Oh!

I am back on my Exceptions recommendations. Why is ApplicationException not valid for Silverlight and why is it no longer recommended? Could you point me to a source?

Asher
GeneralRe: ApplicationException changes
Dewey
1:04 8 Jul '09  
Just google it, but here's the short story.

Try to use ApplicationException in Silverlight... it doesn't EXIST.

Cheers...
GeneralRe: ApplicationException changes
Asher Barak
4:52 8 Jul '09  
Thanks,

Checked it out. A wiered thing...
Will change in next version.

They posted the new version with testing. Enjoy.

Asher
GeneralTest Project
Dewey
10:39 5 Jul '09  
I didn't see it in the zip.
GeneralRe: Test Project
Asher Barak
20:51 5 Jul '09  
It seems they posted an older version of the code. I will check it with the site team.

Thnaks.

Asher
GeneralGetIndexByKey
Reimer van Elst
21:48 26 Jun '09  
I want insert a key2 and value after key1, so i need to know the index of key so i can addAt(index_key1)
But it is not possible to get the IndexByKey.
GeneralRe: GetIndexByKey
Asher Barak
1:28 30 Jun '09  
Hi,

I am publishing an updated version.

Best,

Asher
GeneralIsn't this similar to LinkedList class
SyntaxC4
19:47 21 Jan '09  
The LinkedList class is very similar to this functionality don't you think?

System.Collections.Generic.LinkedList

Just a thought.
AnswerRe: Isn't this similar to LinkedList class
Asher Barak
21:37 21 Jan '09  
I think they are quite different.
The dictionary is randomly accessed by key while the linked list is accessed in ways of moving from one member to the other.

For some reading about data structures try wikipedia at:
http://en.wikipedia.org/wiki/List_of_data_structures[^]

best,

Asher
Generalignore case for string key
Unruled Boy
19:49 20 Jan '09  
for string key, could it be possible to ignore case?

Regards,
unruledboy_at_gmail_dot_com
http://www.xnlab.com

GeneralRe: ignore case for string key
PIEBALDconsult
4:30 21 Jan '09  
The regular Dictionary can do that, you can pass a comparer into the constructor, not sure if this guy has an overload for that constructor though.
AnswerRe: ignore case for string key
Asher Barak
9:28 21 Jan '09  
Hi,

Inherit the collection and override TransformKey(TKey key). It recives the key and trnsformes it anyway you like before using it (in and out).

(or change the function in the original)

Asher
GeneralI like it
Unruled Boy
19:36 20 Jan '09  
right now I am using KeyedCollection, but it has be to inherited, which really takes more time and make it more complicated.

Regards,
unruledboy_at_gmail_dot_com
http://www.xnlab.com

GeneralRe: I like it
Asher Barak
9:30 21 Jan '09  
Thanks
GeneralOh, and
PIEBALDconsult
14:06 20 Jan '09  
Those loose ends really have to be cleaned up.
GeneralUh oh
PIEBALDconsult
13:47 20 Jan '09  
"The Add, Remove and Clear functions are hidden (using the new keyword)"

You used non-polymorphic inheritance?

So if I write:

Dictionary<string, int> dic = IndexedDictionary<string, int>() ;
dic.Add ( "x" , 0 ) ;

the wrong Add method is used?


You should wrap the Dictionary, not derive from it.
AnswerRe: Uh oh
Asher Barak
9:54 21 Jan '09  
Hi,

First, I lookd up the stuff you posted here in the code project. Nice work.
As for your remarks, you are 100% right, though I am not sure I will get to it any time soon. If you (or anyone else here) feel up to it I would be happy (I based mine on someone else's work).

Asher
GeneralAnother approach
Alexander Turlov
11:29 11 Nov '08  
If the main goal was just to have a collection that allows access by both index and key then you can use a standard generics collection that is included in .NET 2.0 framework:

KeyedCollection<tkey,titem>

It belongs to a System.Collections.ObjectModel namesapace and sits inside mscorlib.dll. More information on how to use it on MSDN http://msdn.microsoft.com/en-us/library/ms132438.aspx[^]

Alexander Turlov
MCSD.NET

GeneralRe: Another approach
Paul B.
12:06 11 Nov '08  
KeyedCollection works very nice... Or just use a dictionary and iterate over the dictionary.Values collection.
AnswerRe: Another approach
Asher Barak
18:56 11 Nov '08  
Hi Alexander,

Thanks for your reply.
Please note the "The Other Options" section in my article where I discussed this option.

Best,

Asher
GeneralRe: Another approach
mariahayek
12:31 23 Nov '08  
thanks Alexander...
cc4i.comcd.comcj.coms4w.com
GeneralRe: Another approach
bbender
7:46 15 Jan '09  
First of all, excellent job! The thing to note here is that the KeyedCollection is abstract and yours is not, which means I can use your immediately.

The one things this lacks that I need though is the ability to return an index of a key. That would be sweet.


Last Updated 4 Jul 2009 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010