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.