A little over 2 years ago, I saw my peer’s code that uses the KeyedCollection<TKey, TItem> class. I never seen it before, and it is actually a pretty nice class. It is essentially a dictionary class, but with the stipulation that the key for each item added to the dictionary can be retrieved from the item itself.
With a Dictionary, every time we wanted to add a new item into it, we have to call its Add method, which accepts 2 parameters, the key for the item and the item itself. In most cases, the key already is in the item, so we’re just reduplicating it. This KeyedCollection class provides a shortcut since we can just provide it a method so it can get the key for each item added automatically.
The following code show how Dictionary and KeyedCollection can be used:
// Class to contain data stuff
public class MyData {
public int Id { get; set; }
public string Data { get; set; }
}
// KeyedCollection is an abstract class, so have to derive
public class MyDataKeyedCollection : KeyedCollection<int, MyData> {
protected override int GetKeyForItem(MyData item) {
return item.Id;
}
}
private void Test() {
MyData temp, md = new MyData() { Id = 1, Data = "Test" };
// Using a Dictionary
Dictionary<int, MyData> dict = new Dictionary<int, MyData>();
dict.Add(md.Id, md); // Add to dictionary
temp = dict[1]; // Retrieve
// Using KeyedCollection
KeyedCollection<int, MyData> keyd = new MyDataKeyedCollection();
keyd.Add(md); // Add to KeyedCollection
temp = keyd[1]; // Retrieve
}
As you can see, usage is very, very similar to a Dictionary, with the only difference that the Add method only accepts 1 parameter. Retrieval is exactly the same. However, since KeyedCollection is an abstract class, you cannot use it directly; you have to derive from it and use your derived class – that’s why I created the MyDataKeyedCollection class. This MyDataKeyedCollection class has to override the GetKeyForItem method (which is declared as an abstract method in the base class) so you can retrieve the key for the given item. For me this is somewhat of a deal breaker – I have to always derive from it – there’s no way to use it easily, like a typical collection class.
So my next step is to try to make it easier to consume… thus I created my KeyedCollectionEx class.
public class KeyedCollectionEx<TKey, TItem> : KeyedCollection<TKey, TItem> {
private Func<TItem, TKey> _getKeyForItemDelegate;
public KeyedCollectionEx(Func<TItem, TKey> getKeyForItemDelegate) : base() {
if (getKeyForItemDelegate == null)
throw new ArgumentNullException("Delegate passed can't be null!");
_getKeyForItemDelegate = getKeyForItemDelegate;
}
protected override TKey GetKeyForItem(TItem item) {
return _getKeyForItemDelegate(item);
}
}
The constructor now requires a delegate that will get the TKey for the given TItem – I’m using the Func delegate which is in .NET 3.0, so if you’re using .NET 2.0, you need to create your own delegate signature. With this, my test method becomes as follows:
private void Test2() {
MyData temp, md = new MyData() { Id = 1, Data = "Test" };
// Using KeyedCollectionEx with anonymous delegate
KeyedCollection<int, MyData> keyd = new KeyedCollectionEx<int, MyData>(
delegate(MyData myData) { return myData.Id; });
keyd.Add(md); // Add to KeyedCollection
temp = keyd[1]; // Retrieve
// Using KeyedCollectionEx with lambda expression
KeyedCollection<int, MyData> keyd2 =
new KeyedCollectionEx<int, MyData>(myData => myData.Id);
keyd2.Add(md); // Add to KeyedCollection
temp = keyd2[1]; // Retrieve
}
I demonstrated how to use it with anonymous methods (so I don’t have to create a method for it), and also how to use it with a lambda expression. Much simpler, IMHO – no need to create a derived class. However, I would like this to work with WPF’s data binding – so I’d like further enhance my KeyedCollectionEx class so it can notify WPF when items are being added or removed from it – that’ll be my next post.