To filter items in a generic collection, we use the FindAll method with a match predicate—a predicate that returns true when an item in the collection meets our custom filter criteria.
Let’s dig into some examples. To begin with, we’ll look at how we can use the List.FindAll method to find all employees who are managers by filtering them from a collection of Employees; we’ll name this method GetManagers. The syntax for defining the collection of employees is List
To keep things simple, we’ll assume that we have in place a basic Employee class, and a method called GetEmployees for retrieving Employee objects from the database:
Employee.cs (excerpt)
public class Employee : Person {
public int EmployeeID;
public bool IsManager;
}
public List
return new List
}
Our GetManagers method will first retrieve all employees. We’ll then call FindAll and pass the IsManager method as the match predicate:
public List
{
List
return employeeList.FindAll(IsManager);
}
public bool IsManager(Employee emp)
{
return emp.IsManager == true;
}
Great! The FindAll method iterated through the collection and jumped into the IsManager function for each Employee in the list. Every time the function returned true, FindAll added the Employee to the list of managers.
We can simplify this process slightly by replacing that IsManager method with an anonymous delegate.
Whoah—the jargon is flowing thick and fast now! Let’s break that down:
*
A delegate is a type of function pointer. Unlike the function pointers that are used in languages such as C and C++, delegates are both object oriented and type-safe. For a deeper understanding of delegates, read the MSDN article “An Introduction to Delegates” by Jeffrey Richter.
*
An anonymous delegate is a delegate function that is declared inline.
Some predicates don’t offer any values as reusable functions, so using an anonymous delegate allows us to specify a return value that we can actually use, while at the same time keeping our code simple. Here’s how our GetManagers function looks once we introduce an anonymous delegate predicate:
Predicates.aspx.cs (excerpt)
public List
{
List
return employeeList.FindAll(
delegate (Employee emp)
{ return emp.IsManager == true; }
);
}
As you can see, the result is identical functionality that’s achieved using less code.
There’s another case that might cause you to use anonymous delegates: the situation that arises when a predicate needs additional parameters. List methods like Find and FindAll take a single parameter of type Predicate
To see what I’m talking about, let’s experiment with a function that looks up an Employee by the Employee’s id. Here’s how you might try to write this function without using an anonymous delegate:
public static Employee Get(int id)
{
List
return EmployeeList.Find(
EmployeeMatch(Employee employee, int id)
);
}
// THIS DOESN’T COMPILE – NOT A VALID PREDICATE
public static bool EmployeeMatch(Employee employee, int id)
{
return employee.EmployeeID == id;
}
Unfortunately, that code won’t compile—try, and you’ll receive a series of errors. The compiler can’t make sense of our attempt to pass two parameters to the predicate, and goes looking for additional parentheses and semicolons to compensate. This is because Find(Predicate
Listing 2.5: Predicates.aspx.cs (excerpt)
public Employee Get(int id)
{
List
return employeeList.Find(
delegate(Employee emp)
{ return emp.EmployeeID == id; }
);
}
This procedure is referred to as local variable capturing; in this case it’s the id that has been “captured.”