Using Anonymous Method Predicates to Search Lists

I use System.Collections.Generic.List all over the place.  Generics are a couple of years out of date and most people are done with them in terms of cool factor, but that doesn’t in any way detract from the coolness they add to my life each and every day.  I still eat toast and that’s been around for ages.  I answered a question this morning that made me realize that while the generic list class has been generally embraced by most, it perhaps has also been generally muddled by some attempting to keep up with the oft quick pace that is .NET.  I’ve seen quite a few development shops that stuck around on .NET 1.1 for a long time because of migration concerns and then very quickly “updated their code” to embrace .NET 2.0 without really giving people the time to absorb all of the new information.  One of these areas is with collection classes.

When a development shop moves to .NET 2.0, one of the first things you’ll see is a bunch of {blah}Collection class objects being removed from the codebase and their usage replaced with List throughout.  This is all well and good.  A smaller code surface area is easier to test and maintain, and most people didn’t ever fully finish building a complete collection class anyway, feeling that the code around finding, searching, comparing, etc… within the collection classes was always a little muddy.  It’s true that building a robust collection class is difficult, especially when considering an object model that involves some serious aggregation.

However, we seem to have found a new source for confusion on how to search collections in the adoption of the System.Collections.Generic.List class.  The question I answered this morning about searching a generic list is not the first I have received, so I thought I’d take the time to share a couple of techniques that I use and get feedback from others on their strategies.  What prompted me to write this article was a small snippet of code I received that looked like this:  (original code modified to protect the innocent)

string targetName = “Thompson”;

List myObjectList = new List();
… list is populated with some items …

MyObj selectedObject = null;
foreach (MyObj obj in myObjectList)
{
if (obj.Name == targetName)
{
selectedObject = obj;
}
}

The code looks reasonable from a first glance.  The original code was actually copied from within a collection class’ SearchByName() method when the class was being replaced with a System.Collections.Generic.List.  This is scary for a couple of reasons.  First of all, original code contains a couple of bugs that weren’t addressed as part of the migration probably because very little time was given to “move to .NET 2.0”. &nbp;Secondly, this probably isn’t an isolated case.  I suspect that this kind of refactoring is happening in a lot of other projects.  Let’s look at the code in a little more detail and think about some of the opportunities for improvement.

The first obvious bug is that if two objects exist in the list with the target name, the last one searched that matches is being set as the selected object.  This shouldn’t simply be handled by adding a “return” within the loop to return the first either (which I’ve seen in “fixes” before).  In this case it is probably important to know that more than one object matched the target name.  While seemingly minor, there is no lock statement around the foreach search, meaning that the list could be modified during the search by code in any thread with a reference to the list.  Wouldn’t it be nice if there were a way to find an item in a list without having to worry about such things?

The System.Collections.Generic.List class provides Find() and FindAll() methods that take a System.Predicate as a parameter.  Most developers I’ve watched tend to browse this method in Intellisense, think “Oh my god, I have no idea what this Predicate class is” and then code a method similar to the one listed above.  However, the System.Predicate generic really isn’t all that scary and is actually a really elegant solution to the problem.  Look at the following code for a second:

string targetName = “Thompson”;

List myObjectList = new List();
… list is populated with some items …

List matches = myObjectList.FindAll(delegate(MyObj compare)
{
return compare.Name == targetName;
});

Here, an anonymous method was used as an implementation of System.Predicate to determine whether the comparison of an object’s name to the target name was true.  That’s essentially how predicates work, they assert whether a particular predicate is true or not.  In this case, we are using them to determine whether or not a particular item in a collection matches certain search criteria.  The way that the FindAll() method works is to pass each object in the collection to the specified predicate and add the object to a match list if the predicate is true.  In the one call to FindAll() our code was able to determine all items in the list that have the specified target name.  Also, the Find() and FindAll() method correctly issue a lock statement upon the list to protect the search, as well as other finery around error handling for null objects in the list.  All of this code for free in a single method call.

Anonymous delegates do not have to be used for predicates, although they present the most graceful solution for a parameterized search.  What I mean by this is that if your search does not rely upon a parameter like targetName in the example above, you could pass a delagate implementation to the find method.  The example below shows how a predicate can be used to find items in the list with a name longer than 5 characters:

List myObjectList = new List();
… list is populated with some items …

List matches = myObjectList.FindAll(ObjNameLongEnough);

The method ObjNameLongEnough is defined as follows:

private bool ObjNameLongEnough(MyObj compare)
{
return compare.Name.Length > 5;
}

This example gives a better idea of how the anonymous method delegate is working by showing the signature of the System.Predicate that is expected.  Notice that the method signature of ObjNameLongEnough() takes a MyObj parameter.  The FindAll() method simply calls this delegate for each item in the list and adds the object to the match collection if the predicate returns true.  If the desired behavior was to find only the first match in the list, use the Find() method instead.

Astute readers are probably wondering if there is an overloaded version of System.Predicate that takes an additional search parameter.  This would have allowed for the first example that compared to target name to be coded as an actual delegate as opposed to an anonymous method by passing the targetName variable into the search delegate.  However, unfortunately, there is not an overloaded version.  This appears to stem from the computer science root that a predicate is simply a test that something is true of something.  Therefore a predicate cannot be parameterized as it would be testing its trueness(TM) to something in relation to something else.

IMHO anonymous methods are still a great solution to the problem and provide compact, readable code for searching generic list collections without having to worry about the finer points of locking, object null testing, and first match versus all matches.  Enjoy!

This entry was posted in C# and tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *