Skip Navigation Links / Posts / Post

Predicates as rules

Categories

Generic predicates are a great way to encapsulate the logic for filtering collections.  Recently we had a business application where we had to show various views of a collection based upon certain business rules and generic predicates were the perfect match.  Generic predicates are methods that work on a List<T> and which take a delegate that works on each item in the list to return only the items that conform to a specific piece of logic.  Let's look at an example, we'll start by creating a List<T> which contains 10 items:

List<TestClass> tests = new List<TestClass>();

for (int i = 0; i < 10; i++) {
    tests.Add(new TestClass(i + 1));
}

In the above code snippet we have a List<T> variable named 'tests' which contains 10 elements and within that list each element holds an instance of the TestClass type.  Now let's imagine that this list is a list of Timesheet entries and that we are given a request to display a subset of those entries based on some business rules

The List<T> class has a FindAll method on it which takes a Predicate<T> argument and returns a List<T>.  Inside of the the FindAll method, each item in the List<T> is enumerated and evaluated against the Predicate<T> argument passed in to it.  The Predicate<T> is the place where we can add our business rules to return the subset list.  So based upon the list in the above snippet, let's imagine that we have a business rule to return only TestClass instances that are odd numbered.  To do this we would first write a method which represents our business rule that we can use to inspect a TestClass instance and return true or false based on whether that instance is odd numbered or not.  Such a method might look like this:

public static class BusinessRules {
     public static bool IsOdd(TestClass target) {
          return target.ID % 2 == 1;
     }
}

Because List<T>'s FindAll method takes a Predicate<T>  - a signature that our IsOdd method conforms to - we can use our IsOdd as the filter for the FindAll evaluator on our List<T. collection instance like so:

List<TestClass> odds = tests.FindAll(new Predicate<TestClass>(BusinessRules.IsOdd));

Interestingly we can also replace the argument with a delegate which matches the same signature like so:

List<TestClass> odds = tests.FindAll(delegate(TestClass target) { return target.ID % 2 == 1; });

However I like the first method because it's more self-describing to see a delegate named BusinessRules.IsOdd to infer the meaning of what's going on.  Below is the code for a program which displays the output for 2 lists based on a different set of business rules.  1 list displays even numbers while the other displays odd numbers:

 

private static void RunPredicateRules() {
    List<TestClass> tests = new List<TestClass>();

    for (int i = 0; i < 10; i++) {
        tests.Add(new TestClass(i + 1));
    }

    List<TestClass> evens = tests.FindAll(new Predicate<TestClass>(BusinessRules.IsEven));
    List<TestClass> odds = tests.FindAll(new Predicate<TestClass>(BusinessRules.IsOdd));

    Console.WriteLine("Displaying Evens:");
    evens.ForEach(new Action<TestClass>(DisplayAll));

    Console.WriteLine("Displaying Odds:");
    odds.ForEach(new Action<TestClass>(DisplayAll));

    Console.ReadLine();
}

 

static void DisplayAll(TestClass target) { Console.WriteLine("ID = {0}", target.ID); }

 

public static class BusinessRules {
    public static bool IsOdd(TestClass target) { return target.ID % 2 == 1; }
    public static bool IsEven(TestClass target) { return target.ID % 2 == 0; }
}

public class TestClass {
    public TestClass(int id) { this._id = id; }

    private int _id;
    public int ID { get { return _id; } set { _id = value; } }
}

posted 5/9/2007 9:49:22 PM

 

Comments:

# Wrapped Predicate is
posted by Andrew on 5/10/2007 7:53:27 PM :

Agree. Go a step further: wrap the Predicate<T> in a class, add some operator overloads and implicit conversions to/from Predicate<T> - and with not much extra you can (today) produce something like:

list.FindAll(Where.TestClass.IsOdd() & !Where.TestClass.HasSomeValue("Foo"));

A shame the framework doesn't have such a beast in built... until LINQ that is...

# to Clarify..
posted by James Curran on 5/26/2007 12:43:42 AM :

>> Generic predicates are methods that work on a List<T> and which take a delegate that works on each item in the list to return only the items that conform to a specific piece of logic. <<

Not exactly. The "predicate" (from the Latin, "to dicate before") is the delegate : The condition which must be met before further action is taken. With FindAll, that action is to return the items that confirm, but that's not universal. With List<T>.RemoveAll, it's just the reverse.

Further, it's not true that they "work on a List<T>". Specifically, a Predicate<T> works on an T object. Ideally, the concept works best when used repeatedly on a collection of Ts, but where you get that collection is up to you.


On a different note, I experimented with your BusinessRules class, and the format I think I like best is:


public static class BusinessRules
{
private static bool isOdd(TestClass target)
{
return target.ID % 2 == 1;
}

public static Predicate<TestClass> IsOdd
{
get
{
return new Predicate<TestClass>(isOdd );
}
}
}

That would allow:

List<TestClass> odds = tests.FindAll(BusinessRules.IsOdd);



 

Comments are currently disabled for this post.