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.