Writing flexible filters for your data using Predicates

Dominic Burford
4 min readJul 16, 2019

In lambda calculus, a predicate is an expression that evaluates to either true of false. If you have written any LINQ or a SQL query you have probably written these types of expressions already. If you have written a SQL query that contains a WHERE clause for example, this is a type of predicate. If you’ve ever used LINQ to filter the contents of a list, this too is an example of a predicate.

Whether you realise it or not, you have probably already used predicates in your code. Whenever you have a need to filter the items in a dataset and / or list, then it is common to use predicates to do this. The notion of a predicate is widely used and understood, even if you weren’t necessarily aware of them.

Within the .NET Framework the notion of a predicate is formally identified by

Hide Copy Code

Predicate<T>

This is a functional construct providing a convenient way of testing the truthy or falsity of a given expression relating to an instance of type T. If you’re familiar with delegates then Predicate<T> is equivalent to

Hide Copy Code

Func<T, bool>

For example suppose we have a Car class that represents T. Each instance of T (Car) contains the properties Colour (red, green, black etc) and EngineSize (1000, 1200, 1600 cc etc).

Hide Copy Code

public class Car
{
public string Colour { get; set; }
public int EngineSize { get; set; }
}

Let’s assume that we have a SQL query that returns a list of all the cars registered for a particular year.

Hide Copy Code

var data = new DataService();
List<Car> = data.GetAllRegisteredCars(new DateTime(2019, 01, 01);

The above query will return all cars registered during the year of 2019.

Suppose we want to filter that list of cars to just those that meet certain criteria e.g. those cars with an engine size of 1600cc or are blue in colour. To filter the data we would use predicates as follows.

Hide Copy Code

var matches1 = cars.FindAll(p => p.EngineSize == 1600);
var matches2 = cars.FindAll(p => p.Colour == "Blue");

We could hardcode the predicates and leave them in the code as in the above examples. However, a benefit of using Predicate<T> in your code is that it gives you the ability to separate the data from the expressions used to filter it. Instead of hardcoding filters in your code, you can define these elsewhere and bring them into your code when needed.

Let's assume we have a completely separate class that defines our predicates called PredicateFilters.cs

Hide Copy Code

public static class PredicateFilters
{
public static Predicate<Car> FindBlueCars = (Car p) => p.Colour == "Blue";
public static Predicate<Car> Find1600Cars = (Car p) => p.EngineSize == 1600;
}

In our data code we would now write the following code to filter the cars.

Hide Copy Code

var matches1 = cars.FindAll(PredicateFilters.Find1600Cars);
var matches2 = cars.FindAll(PredicateFilters.FindBlueCars);

We can see even from this simple example that separating our queries from our code is straight-forward. We no longer need to pollute our code with hardcoded filters. We also have the ability to reuse those filters elsewhere. For example, we may have more than one function that needs to know which cars are blue. We write the filter once and use it everywhere we need it. If in the future it turns out that it’s red cars we need instead of blue, we can change the filter in one place without having to change any of our data code.

Our filters may return a single item or may return a list of items. Alternatively, we may also want to know the number of items returned by our filter. We would probably want to do this for different types of data e.g. cars, drivers, orders etc. This is where we need to get a bit smarter with how we design our filters to allow them to work with different types of data.

Let’s start by implementing an interface that defines the filters we want to execute on our data.

Hide Copy Code

public interface IPredicateValue<T>
{
T GetValue(List<T> list, Predicate<T> filter);
List<T> GetValues(List<T> list, Predicate<T> filter);
int GetCount(List<T> list, Predicate<T> filter);
}

Here we have defined an interface that takes a type of T. The functions will provide the following functionality.

- T GetValue(List<T> list, Predicate<T> filter) - return a single instance of T for the filter
- List<T> GetValues(List<T> list, Predicate<T> filter) - returns a list of T for the filter
- int GetCount(List<T> list, Predicate<T> filter) - returns the count of items of T that match the filter

For each type of data that we want to filter, we should implement this interface. This will provide a consistent set of methods that we can use to filter our data.

Hide Copy Code

public class CarPredicate : IPredicateValue<Car>
{
public Car GetValue(List<Car> list, Predicate<Car> filter)
{
if (list == null || !list.Any() || filter == null) return null;
return list.Find(filter);
}
public List<Car> GetValues(List<Car> list, Predicate<Car> filter)
{
if (list == null || !list.Any() || filter == null) return null;
return list.FindAll(filter);
}
public int GetCount(List<Car> list, Predicate<Car> filter)
{
if (list == null || !list.Any() || filter == null) return 0;
return list.FindAll(filter).Count;
}
}

We can now filter our data as follows.

Hide Copy Code

//fetch all registered cars for the current year
var data = new DataService();
List<Car> = data.GetAllRegisteredCars(new DateTime(2019, 01, 01);
//filter the cars on just the blue ones
var predicatevalue = new CarPredicate();
var blueCars = predicatevalue.GetValue(data, PredicateFilters.FindBlueCars);

Keeping your code and your predicates separate gives you far more flexibility, as well as giving you a single point of change should one of the expressions used to query your data need to change. You can implement filters for any / all types of data with the added benefit that it allows you to filter your data in a consistent manner.

If you want to get serious about how you filter data, then give predicates a try.

--

--

Dominic Burford
Dominic Burford

Written by Dominic Burford

A father, cyclist, vegetarian, atheist, geek and multiple award winning technical author. Loves real ale, fine wine and good music. All round decent chap.

No responses yet