Steve Michelotti

A .NET Developer's Toolbox

  Home  |   Contact  |   Syndication    |   Login
  175 Posts | 0 Stories | 939 Comments | 51 Trackbacks

News

View Steve Michelotti's profile on LinkedIn

profile for Steve Michelotti at Stack Overflow, Q&A for professional and enthusiast programmers




Google My Blog

What I'm Reading:

Shelfari: Book reviews on your book blog

Tag Cloud


Archives

Post Categories

Code

Publications

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:

   1:  public class Person
   2:  {
   3:      public Guid ID { get; set; }
   4:      public string FirstName { get; set; }
   5:      public string LastName { get; set; }
   6:  }

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:

   1:  public class PersonKeyedCollection : KeyedCollection<string, Person>
   2:  {
   3:      protected override string GetKeyForItem(Person item)
   4:      {
   5:          return item.FirstName;
   6:      }
   7:  }

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:

   1:  var list = new PersonKeyedCollection();
   2:  list.Add(new Person() { FirstName = "Steve" });
   3:  list.Add(new Person() { FirstName = "Bob" });
   4:  list.Add(new Person() { FirstName = "Jim" });
   5:   
   6:  Console.WriteLine("Result by key  : " + list["Steve"].ToString());
   7:  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:

   1:  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:

   1:  public class GenericKeyedCollection<TKey, TItem> : KeyedCollection<TKey, TItem>
   2:  {
   3:      private Func<TItem, TKey> getKeyFunc;
   4:   
   5:      protected override TKey GetKeyForItem(TItem item)
   6:      {
   7:          return getKeyFunc(item);
   8:      }
   9:   
  10:      public GenericKeyedCollection(Func<TItem, TKey> getKeyFunc)
  11:      {
  12:          this.getKeyFunc = getKeyFunc;
  13:      }
  14:  }

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.

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati
posted on Friday, January 23, 2009 4:14 PM

Feedback

# re: Use Lambda expression to create a generic KeyedCollection 1/25/2009 8:20 AM Luke Sandell
Nice job... I have done the exact same thing.

I also becoming a fan of the ToDictionary() and ToLookup() extension methods on IEnumerable for creating custom dictionaries on the fly.

# re: Use Lambda expression to create a generic KeyedCollection 1/27/2009 5:25 PM BEM
Very cool! However, without a null constructor, the collection cannot be serialized using XmlSerializer.


# re: Use Lambda expression to create a generic KeyedCollection 1/27/2009 9:11 PM Steve
Yep, that's true. If you need a default constructor you could do this:
public class Person2KeyedCollection : GenericKeyedCollection<string, Person>
{
public Person2KeyedCollection() : base(p => p.FirstName) {}
}

but at that point, there's not a whole lot of difference between that and the "normal" implementation because you're sub-classing either way.

# re: Use Lambda expression to create a generic KeyedCollection 12/16/2009 2:47 AM Nelson P. Varghese
Nice one. As the KeyedCollection<TKey, TItem> class stores the key in the lookup dictionary internally, the key should be immutable.

# Generic KeyedCollection in VB.NET 11/18/2010 9:46 AM Brett Riester
Imports System.Runtime.CompilerServices

Public Module modKeyedCollection

<ExtensionAttribute()> _
Public Function ToMyKeyedCollection(Of TKey, TItem)(ByVal lst As IEnumerable(Of TItem), ByVal getKeyForItem As Func(Of TItem, TKey)) _
As MyKeyedCollection(Of TKey, TItem)
Dim col = New MyKeyedCollection(Of TKey, TItem)(getKeyForItem)
For Each item In lst
col.Add(item)
Next
Return col
End Function

End Module

Public Class MyKeyedCollection(Of TKey, TItem)
Inherits Collections.ObjectModel.KeyedCollection(Of TKey, TItem)

Protected _GetKeyForItem As Func(Of TItem, TKey)
Public Sub New(ByVal getKeyForItem As Func(Of TItem, TKey))
_GetKeyForItem = getKeyForItem
End Sub

Protected Overrides Function GetKeyForItem(ByVal item As TItem) As TKey
Return _GetKeyForItem(item)
End Function

End Class


Post A Comment
Title:
Name:
Email:
Website:
Comment:
Verification: