Fadzai Chamba

I disagree with everything I just said

Predicates and Lambda functions for filtering and searching

One of the first things I looked for in the DataSet after I moved to .NET was the equivalent of the Filter property of the ADO recordset. I relied on such functionality a lot, and in many cases I still do. It took just a short time for me to find out that you have to access the RowFilter property of the DataSet's DataView. The syntax in both cases was similar, you construct an SQL like string (similar to what would go into the WHERE clause of a SELECT query). So with this, you could get all the rows from a table and then as the user enters some filter criteria, you can narrow the results down to what matches the user's filter.

Before I go into this, I must say it isn’t advisable to get all the rows from every table, you must get only what you need. But there are some cases where it isn’t too expensive to get all the rows, typically for lookup fields (think Account Types, States, Cities etc.), but it could be inefficient to repeatedly go to the database for just what you want. After my first .NET project I started to look for better ways of achieving this, at least ways to do so without relying on a DataTable, DataSet or DataView.

One such way is using Delegates against a method you can find on some of the IEnumerable(Of T)/IEnumerable<T> classes, such as a List. The delegate in question is Predicate(Of T)/Predicate<T>. Anyone who has done some PROLOG before could be thinking, "Hmm, predicates hey?" these are not quite the same thing. The predicate delegate is simple enough; it takes an argument of type T, and returns a Boolean value. Your preferred IEnumerable class will call your method each time it needs to make a comparison when searching for something.

There are two methods I will cover here, the Find() and FindAll() methods. Find will return only one item, the first item in your List that matches your criteria, and FindAll is what you would use when filtering, it returns everything that is a match. Before LINQ came about, and if you weren’t using anonymous methods in C#, you had to write a full method body for this comparison. That is the technique I will show first. Don’t fret if you don’t see your favorite language, my samples are always in both C# and VB, though my articles may have only VB because that is my favorite language.

Full Method Body

Private Function CompareName(ByVal prod As Filter.Common.Product) As Boolean

    If MethodNameFilter.Text = "" Then Return True

 

    'do the the filter...

    Return prod.ProductName.StartsWith(MethodNameFilter.Text, _                                        StringComparison.InvariantCultureIgnoreCase)

End Function 

 

The code above is an example of how a VB method that conforms to the Predicate(Of Filter.Common.Product) will look like. In this case, I am looking for products with a name starting with the text inside a textbox named MethodNameFilter. The method is called like so:

filtered = filtered.FindAll(AddressOf CompareName)

 

In C#, the equivalent code to call the method is slightly different:

filtered = filtered.FindAll(new Predicate<Filter.Common.Product>(CompareName));

 

VB allows you to write code just like this, where you create an instance of the Predicate<T> for some T, and then specify the method, but it has the convenient syntax of just saying AddressOf and then it will do the rest because it will know what delegate to use, and it can verify the method signature as you type.

By specifying the method in the call to FindAll(), you are telling the List (in this case I used List<T>) to use the specified method to qualify any objects for the new filtered list. I used the variable name filtered to store my copy of the filtered down list, while preserving a copy of the original list in case the user cleared the filter text. Here is a screenshot to show you what it looks like at runtime.


 

I allow the user to specifying any combination of two filter criteria, Name and Supplier for the Method body, and Name and Category for the Lambda expression tab, and in both cases I then filter the list down to show only what is a match. As you can see from the screenshot, there is an auto complete list for the Supplier textbox. I included code to create that in the attached download to show how you could assist a user. The Category textbox also has one. I've had to do that with info that was not normalised in the database, I get a list of all existing values for a column and then suggest to the user, as they are typing, what values to pick from.

Using a Lambda Expression

Doing the same thing in VB using a lambda expression is like this:

filtered = filtered.FindAll(Function(prod) _

       prod.ProductName.StartsWith(LambdaNameFilter.Text, _

       StringComparison.InvariantCultureIgnoreCase))

 

And in C#,

filtered = filtered.FindAll(prod =>

                        prod.ProductName.StartsWith(LambdaNameFilter.Text,

                        StringComparison.InvariantCultureIgnoreCase));

 

In both cases, I use the inference capacities of both compilers to avoid typing a lot of code; you can see an example of how to do so without Local Type Inference in the comments in the attached download.

These lambdas do the same thing as the method body, and they both match the signature of the Predicate(Of T)/Predicate<T> delegate.

Searching for a specific item

The above examples covered how to use this technique to get all matching items. There are times however when you may want to get only one item. The List class comes with a corresponding Find() method that accepts a Predicate(Of T) argument, and then returns a single result of type T. the return value can be Nothing (null in C#) if there is no match, it if there is more than one item satisfying the criteria, the method will return only the first one. The method bodies and lambdas look exactly the same as before, because it is the same type of comparison using the very same Delegate, so I will not include the code here.

Conclusion

The data in this case as you can see from the screenshot is from the Northwind same database. Just in case someone out there has not installed the database, I included an XML file with the data that a shared library (shared between the VB and C# windows applications) reads to construct the list. I read the data using LINQ to XML in VB with the very sexy XML literals. I also included the project that created this XML file and you can run it by right-clicking on it, Selecting debug, and then Start New Instance. It runs, creates the file and loads it in IE, or your default XML viewer. You may have to change your connection string to get from your copy of Northwind.

Other List(Of T) methods that use the Predicate(Of T) delegate are, FindIndex, FindLast, FindLastIndex, RemoveAll and TrueForAll. FindIndex and FindLastIndex return the zero based index of the first or last occurrence of a matching element respectively. FindLast does the opposite of Find; it returns the last matching element in the list. RemoveAll will remove all elements that match the criteria that are matched by the predicate you have specified. And finally the TrueForAll method will return true if all items in the list match the given criteria. It will call the predicate for each element in order and will stop processing the first time false is returned.

Have a look at the attached download project if you wish to see more about how to use this technique in your own code. Hope this helps, and happy coding.

 

 

Published Sun, Oct 11 2009 4:15 PM by Fadzai Chamba

Comments

# re: Predicates and Lambda functions for filtering and searching @ Friday, October 30, 2009 3:25 PM

I apologize for having no code formatting. I've had to re-install Windows on my PC and am now getting strange behavior with Visual Studio, I haven't had time to configure Windows Live Writer and any of the plugins that can format the code.

As soon as this is done, I will edit it and modify this post.

Leave a Comment

(required) 
(required) 
(optional)
(required) 
Please add 4 and 1 and type the answer here: