Click here to Skip to main content
15,891,136 members
Articles / Programming Languages / C#
Article

Create Predicates from Expressions for Use with Generic Collections

Rate me:
Please Sign up or sign in to vote.
4.48/5 (10 votes)
18 Aug 2006CPOL2 min read 56.1K   453   27   13
Create Predicates from Expressions for use with Generic collections

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

C#
//select out the Employee's whose last name is Brown:
//By Hand
List<Employee><employee /> matchesLastNameByHand = new List<Employee><employee />();
foreach(Employee e in employeeList)
{
    if (e.LastName == "Brown")
           matchesLastNameByHand.Add(e);
}

//Use a predicate
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.

C#
LastName = Smith

or

C#
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

C#
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.

C#
static void Main(string[] args)
{
    //add some employees:
    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");
    //output them in a manual loop:
    foreach (Employee e in employeeList)
        Console.WriteLine(e.ToString());
    //OR 
    //output them with an action and the ForEach method:
    employeeList.ForEach(new Action<Employee>(Employee.Print));
    Console.WriteLine("\n\n\n");
    //select out the Employee's whose last name is Brown:
    //By Hand
    List<Employee> matchesLastNameByHand = new List<Employee>();
    foreach(Employee e in employeeList)
    {
        if (e.LastName == "Brown")
            matchesLastNameByHand.Add(e);
    }
    //Use a predicate
    List<Employee> matchesLastName = 
        employeeList.FindAll(PredicateBuilder.Build<Employee>("LastName = Brown"));
    //output the matches
    Console.WriteLine("The Brown's:\n");
    matchesLastName.ForEach(new Action<Employee>(Employee.Print));
    Console.WriteLine("\n\n\n");
    //everybody making $50000 or more
    List<Employee> matches50K = 
        employeeList.FindAll(PredicateBuilder.Build<Employee>("Salary >= 50000"));
    //output the matches
    Console.WriteLine("People making 50k or more:\n");
    matches50K.ForEach(new Action<Employee>(Employee.Print));
    Console.WriteLine("\n\n\n");
}

Output of Demo

Image 1

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer
United States United States
Working to keep a technology company up to date. Wondering when Microsoft will hire a fresh, innovative guy to run the company.

Comments and Discussions

 
QuestionHow to pass multiple tokens Pin
pramodgupta2428-May-08 1:06
pramodgupta2428-May-08 1:06 
QuestionAny word on the dynamic methods? Pin
Lance May23-Jul-07 3:30
Lance May23-Jul-07 3:30 
GeneralStrings can be fetch from database or fiels Pin
ZAky24-Aug-06 2:15
ZAky24-Aug-06 2:15 
GeneralRe: Strings can be fetch from database or fiels Pin
Tim Kohler24-Aug-06 4:07
Tim Kohler24-Aug-06 4:07 
I didn't think of that being an application, but it certainly could be.

It might make the performance hit (building multiple dyn assemblies) more palatable if they were cached by expression. That way if you passed in Employee lastname = Brown, it would remember that it generated one for that criteria and would re-use it.


I'm having an issue getting DynamicMethod to work as it requires me to generate the il directly and I am not sure how to accomplish that. Still looking into it.



GeneralI think you missed something Pin
Testosteles21-Aug-06 17:02
Testosteles21-Aug-06 17:02 
GeneralRe: I think you missed something Pin
Tim Kohler22-Aug-06 2:46
Tim Kohler22-Aug-06 2:46 
GeneralRe: I think you missed something Pin
Testosteles22-Aug-06 6:47
Testosteles22-Aug-06 6:47 
GeneralDynamic Methods Pin
Craig G. Wilson18-Aug-06 10:48
Craig G. Wilson18-Aug-06 10:48 
GeneralRe: Dynamic Methods Pin
Tim Kohler18-Aug-06 16:33
Tim Kohler18-Aug-06 16:33 
Generalassemblies... Pin
Christian Klauser18-Aug-06 9:31
Christian Klauser18-Aug-06 9:31 
GeneralRe: assemblies... Pin
Tim Kohler18-Aug-06 9:54
Tim Kohler18-Aug-06 9:54 
GeneralRe: assemblies... Pin
Christian Klauser18-Aug-06 11:37
Christian Klauser18-Aug-06 11:37 
GeneralPerformance... Pin
Marc Leger18-Aug-06 8:56
Marc Leger18-Aug-06 8:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.