Using Predicate
Posted on May 14th, 2009
Unless you have been living under a rock or unable to use a newer version of .net since 1.1 you have probably run across the Predicate<T> object while using the List<T>. I know lots of people that use these Lists and there are other objects in the framework that use Predicate<t> as well. Another common one would be the Array object. The basic usage of the Predicate<T> object is to provide a delegate with a method that takes as a parameter the same data type of the object in your list. Then the List, Array, or some other object will essentially enumerate over your collection and test each object with this method. Thus the reason it returns a boolean.
What I’m going to show you here is a simple find process that will allow you to create a reusable delegate for use in the Predicate<T> processes using reflection.
First let’s get our pieces in place. Lets first see how to do it inline which is what most people use by default.
First lets define our person class we will use.
class Person { private string _firstName; private string _lastName; private int _age; public Person(string firstName, string lastName, int age) { this._firstName = firstName; this._lastName = lastName; this._age = age; } public string FirstName { get { return this._firstName; } set { this._firstName = value; } } public string LastName { get { return this._lastName; } set { this._lastName = value; } } public int Age { get { return this._age; } set { this._age = value; } } }
Next in our main app lets add some data.
List<Person> people = new List<Person>(); people.Add(new Person("John", "Smith", 35)); people.Add(new Person("Caitlin", "Smith", 13)); people.Add(new Person("Steve", "Long", 23)); people.Add(new Person("Justin", "Short", 45)); people.Add(new Person("Karigan", "Patterson", 16));
Now that we have data in our object we want to search. First I will show you the delegate method in-lined.
// Find one result Person p = people.Find(delegate(Person p1) { if (p1.LastName == "Long") return true; else return false; }); Console.WriteLine("{0}, {1} - {2}", p.LastName, p.FirstName, p.Age);
As you can see we have created a delegate using the delegate keyword and we are passing in a object of the same datatype as our list as a parameter. Since we are doing a simple find operation we are looking for just 1 person with a last name of Long.
This statement is a little long but not too bad in the grand scheme of things. However what if you were writing an application where you had to do a lot of finds based on just 1 property. That in itself would be come very tedious and you would end up with a lot of repetitive code.
So now lets build a class that we can use to help us with this.
public class SimpleFind<T> { private string _property; private object _valueToFind; private PropertyInfo _p; public SimpleFind(string property, object value) { this._property = property; this._valueToFind = value; this._p = typeof(T).GetProperty(this._property); Protect.Against<nullReferenceException>(p == null, string.Format("Property {0} not found on type {1}", this._property, typeof(T).FullName)); } public bool Find(T t) { try { if (this._p.GetValue(t, null).Equals(this._valueToFind)) { return true; } else { return false; } } catch { return false; } } }
So let’s go over the class itself. First, you should notice that the class uses Generics in the class definition. The type you specify needs to match the type you use in your list. Next is the constructor. Since the delegate needs to take the type of object that matches your data type we need to pass in the information we need in the constructor. In this case since we are trying to find data based on a property value we specify the Property Name that we are going to search against and the Value we want to search for. Now the find method does all the real work though. In the find method you see we are getting the property via reflection and making sure that we actually have that property before we use it. The Protect object was discussed here. Other than that you will notice that the code pretty much matches what we did earlier.
Now to see the difference:
// Find one result Person p = people.Find(new SimpleFind<Person>("LastName", "Long").Find); Console.WriteLine("{0}, {1} - {2}", p.LastName, p.FirstName, p.Age);
As you can see it’s a bit shorter and highly reusable. So what if you wanted to find more than one record. The same class can be used again.
// Find one result List<Person> p2 = people.FindAll(new SimpleFind<Person>("LastName", "Smith").Find); Console.WriteLine(p2.Count.ToString());
Well that’s it. It’s a pretty straight forward process. However I find it very useful and easy to use and the nice thing about it is that since it’s an object you could re-use it with just a little bit of tweaking.
Enjoy.
*** UPDATE ***
After doing some more testing with this class I found that I needed to move the Property Info object up into the constructor to improve performance and reduce overhead. As well I moved the protect statement to the constructor as well as any error was causing the system to just return false and keep trying to process the find. It should stop. Last but not least the If condition in the find method was changed to just use the .Equals method.
You could handle your approach using the new C# 3.0 feature of Lambda Expressions as follows:
Person person = people.Find(p => p.LastName == “Long”);
This should return you a single instance of a person. You could also use the List.FindAll method to search for all persons meeting a certain criteria.
List oldPeople = people.FindAll(p => p.Age > 70);
I agree that you could use lambdas and they would be smaller. However the main reason I show it this way is that there are still companies that have not upgraded to using c# 3.0 as of yet. If u happen to work somewhere that is using the latest framework I would definitly use lambdas as they are small and concise.