Introduction
I'm really a SQL nut. I love working in terms of sets and try to avoid looping where possible in favor of set based operations. One thing that caught my eye is the predicates and actions that are now in the 2.0 Framework. They are part of generics.
Check out the MSDN Magazine article for more details on actions and predicates.
Searching a Collection
I often find myself wanting to pull out certain things from a collection based on criteria. Somewhat like a SQL select. Understand though that this is still a sequential search no matter if you do it by hand or using the predicate.
Sample of Searching by Hand vs Using Predicates
List<Employee><employee /> matchesLastNameByHand = new List<Employee><employee />();
foreach(Employee e in employeeList)
{
if (e.LastName == "Brown")
matchesLastNameByHand.Add(e);
}
List<employee /> matchesLastName =
employeeList.FindAll(PredicateBuilder<Employee>.Build<employee />("LastName = Brown"));
I believe the predicate is quite a bit more elegant. Based on doing a set of runs on various collections and criteria, I have found a slight performance improvement when searching with a predicate vs by hand. So you get more elegant code and a little better performance.
The PredicateBuilder Class and Expressions
You can easily code predicates by hand based on what you need. Just remember their signature returns bool
and takes a single argument of the type stored in the collection you will use them on.
However, generating them dynamically seemed interesting to me as it would allow me to have a very simple SQL-like way to pull matches out of a collection.
To accomplish this, I needed to dynamically generate a predicate from an expression. For simplicity, I greatly simplified the expression grammar to only allow 3 tokens to be passed in: property operator value, i.e.
LastName = Smith
or
Salary >= 50000
There are several shortcomings here, especially with say string
properties with a space in them. Future modifications might support such scenarios.
Examples of Finding in a Collection using PredicateBuilder
List<Employee> matches50K =
employeeList.FindAll(PredicateBuilder.Build<Employee>("Salary >= 50000"));
List<Employee> matchesLastName =
employeeList.FindAll(PredicateBuilder.Build<Employee>("LastName = Brown"));
Building the Predicate with CSharpCodeProvider
The technique for turning the expression into a runnable predicate is accomplished using the CSharpCodeProvider
class.
Essentially we build a class that exposes a static GetPredicate
method. After generating the assembly, we use reflection to invoke that method. We cast the invoke's result appropriately and return that to the caller.
Take a look at the
PredicateBuilder
class in the attached code for more.
Demo Application
The demo app builds up a generic list and then runs a couple of expressions on it to pull out matches.
static void Main(string[] args)
{
List<Employee> employeeList = new List<Employee>();
employeeList.Add(new Employee("John", "Doe", 55000));
employeeList.Add(new Employee("Larry", "Jones", 65000));
employeeList.Add(new Employee("Wayne", "Smith", 25000));
employeeList.Add(new Employee("Sally", "Johnson", 35000));
employeeList.Add(new Employee("Maggie", "Brown", 60000));
employeeList.Add(new Employee("David", "Brown", 80000));
Console.WriteLine("All Employees: \n");
foreach (Employee e in employeeList)
Console.WriteLine(e.ToString());
employeeList.ForEach(new Action<Employee>(Employee.Print));
Console.WriteLine("\n\n\n");
List<Employee> matchesLastNameByHand = new List<Employee>();
foreach(Employee e in employeeList)
{
if (e.LastName == "Brown")
matchesLastNameByHand.Add(e);
}
List<Employee> matchesLastName =
employeeList.FindAll(PredicateBuilder.Build<Employee>("LastName = Brown"));
Console.WriteLine("The Brown's:\n");
matchesLastName.ForEach(new Action<Employee>(Employee.Print));
Console.WriteLine("\n\n\n");
List<Employee> matches50K =
employeeList.FindAll(PredicateBuilder.Build<Employee>("Salary >= 50000"));
Console.WriteLine("People making 50k or more:\n");
matches50K.ForEach(new Action<Employee>(Employee.Print));
Console.WriteLine("\n\n\n");
}
Output of Demo
Summary
The attached solution contains both the predicate builder class as well as a simple demo application. I believe that using expressions to simulate set based operations on collections is an interesting concept and can certainly lead to more elegant and performant code.
To use the PredicateBuilder
, reference the Expressions assembly and then use it to dynamically build your predicates.
History
- 18th August, 2006: Initial post
Working to keep a technology company up to date. Wondering when Microsoft will hire a fresh, innovative guy to run the company.