Use Lambda expression to create a generic KeyedCollection

The .NET framework ships with an abstract KeyedCollection<TKey, TItem> class.  Basically, all you have to do is implement the GetKeyForItem() method you can could look up any item either by ordinal or a customized key. For example, suppose you had a Person class like this:

public class Person
{
    public Guid ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

And suppose you wanted to be able to lookup a Person in the collection either by index or by the FirstName property (yes, a contrived example). You could sub-class the KeyedCollection class like this:

public class PersonKeyedCollection : KeyedCollection<string, Person>
{
    protected override string GetKeyForItem(Person item)
    {
        return item.FirstName;
    }
}

Which would allow you the get a person from the list either by index or by key (in this case the FirstName is my little demo example) like this:

var list = new PersonKeyedCollection();
list.Add(new Person() { FirstName = "Steve" });
list.Add(new Person() { FirstName = "Bob" });
list.Add(new Person() { FirstName = "Jim" });
 
Console.WriteLine("Result by key  : " + list["Steve"].ToString());
Console.WriteLine("Result by index: " + list[0].ToString());

This is all well and good but it becomes a pain when you want to do this for different types and you end up sub-classing the KeyedCollection class a bunch of times just to be able to specify the property you want to use for the key.  You also don't want a solution that relies on Reflection by specifying the name of the property and invoking it on each call.  Alternatively, you can create a more re-usable solution utilizing generics and lambdas so you can write your code like this:

var list = new GenericKeyedCollection<string, Person>(p => p.FirstName);

Now you won't have to sub-class KeyedCollection ever again because you've created a re-usable GenericKeyedCollection class.  Here is the complete code implementation:

public class GenericKeyedCollection<TKey, TItem> : KeyedCollection<TKey, TItem>
{
    private Func<TItem, TKey> getKeyFunc;
 
    protected override TKey GetKeyForItem(TItem item)
    {
        return getKeyFunc(item);
    }
 
    public GenericKeyedCollection(Func<TItem, TKey> getKeyFunc)
    {
        this.getKeyFunc = getKeyFunc;
    }
}

There are a couple of key points.  First, we simply utilize the generic System.Func<T, TResult> class that was introduced in .NET 3.5. We use a lambda expression to pass a delegate into the constructor that will be used on each GetKeyForItem() call.  Then inside GetKeyForItem() we simply invoke the delegate passing in the current item.

Tweet Post Share Update Email RSS