I’ve been sick the last two days (I got a cold during the hottest part of the year so far, go figure) so I had some time to play around with C# 2.0′s generics implementation. What originally got me started was having an old project lying around that had out of date subversion information. Rather than going through each and every sub-directory and deleting .svn directories, I figured I’d write a little one-off app to do it for me.
I started out by attempting to implement the specification pattern, which led to various attempts at generic implementations that were never quite right. Fortunately, while searching for information on generics I stumbled across Joe Walnes post on The power of closures in C# 2.0 which details the use of predicate generic delegates.
For those not familiar with the specification pattern, it basically just allows you to filter a list of objects while keeping your condition separate from your loop. As an example, we often see something like this:
1 public IList FindAllMaleCustomers()
3 IList matches = new ArrayList();
4 foreach(Customer customer in Customers)
11 return matches;
The specification pattern removes that customer.IsMale check out into a specification object, so we can now write something like this instead:
42 public IList FindAllMaleCustomers()
44 return new CustomerFinder(Customers).FindAll(new MaleCustomerSpecification());
Therefore, if the definition of a “Male” ever changes (bad example, I know) we can just change our MaleCustomerSpecification object.
Fortunately for me, .NET 2.0 includes a new type of delegate called the predicate generic delegate, which
Represents the method that defines a set of criteria and determines whether the specified object meets those criteria.
Seems like it’s exactly what we need. So rather than writing a specific specification object, we can just write a generic predicate like so:
1 public class Spec
3 public static Predicate<DirectoryInfo> SubversionDirectory
7 return delegate(DirectoryInfo dirInfo)
9 return dirInfo.Name == “.svn”;
Which we then use like this:
1 new DirectoryFinder(directories).FindAll(Spec.SubversionDirectory);
Our DirectoryFinder object is a little more complex than a standard FindAll on a method like System.Collections.Generic.List would be, because we recurse all the way through the sub-directories of a given directory:
1 public class DirectoryFinder
3 private IList<DirectoryInfo> _directories;
5 public DirectoryFinder(IList<DirectoryInfo> directories)
7 _directories = directories;
10 public DirectoryFinder(DirectoryInfo directory)
12 _directories = directory.GetDirectories();
15 public IList<DirectoryInfo> FindAll(Predicate<DirectoryInfo> predicate)
17 return FindAll(predicate, _directories);
20 private IList<DirectoryInfo> FindAll(Predicate<DirectoryInfo> predicate, IList<DirectoryInfo> directories)
22 List<DirectoryInfo> matches = new List<DirectoryInfo>();
23 foreach (DirectoryInfo info in directories)
25 if (predicate(info))
31 matches.AddRange(FindAll(predicate, info.GetDirectories()));
34 return matches;
I’m almost certain there’s other ways of doing this, some are probably better as well. For another use of predicates, check out Jean-Paul Boodhoo’s post on validation in the domain layer, where he uses them to flesh out business rules.