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.