Predicate Delegates
Vaibhav • September 11, 2025
In the previous article, we explored closures and captured variables - learning how lambdas can retain access to variables from their outer scope and maintain state across invocations. This concept is foundational to many functional programming patterns in C#, including filtering, searching, and decision-making. One of the most practical applications of these ideas is through the use of predicate delegates.
Predicate delegates are specialized delegates that return a boolean value - typically used to test whether a condition is true for a given input. They are widely used in collection methods, event filtering, and custom logic pipelines. In this article, we’ll explore what predicate delegates are, how they work, and how to use them effectively in your C# programs. We’ll also look at how they relate to lambda expressions, closures, and higher-order functions.
What Is a Predicate Delegate?
A predicate delegate is a function that takes a single input and returns a bool
. It answers a yes/no question about the input - for example, “Is this number
even?” or “Does this string contain a keyword?”.
C# provides a built-in generic delegate for this purpose:
public delegate bool Predicate<T>(T obj);
This delegate is defined in the System
namespace and is used extensively in
collection methods like List<T>.Find
, List<T>.RemoveAll
, and Array.Exists
.
Using Predicate with Lists
Let’s start with a simple example. Suppose you have a list of integers and you want to find the first even
number. You can use List<T>.Find
with a predicate:
List<int> numbers = new List<int> { 1, 3, 4, 7, 10 };
int firstEven = numbers.Find(n => n % 2 == 0);
Console.WriteLine(firstEven); // Output: 4
The lambda n => n % 2 == 0
is a predicate - it returns true
for even numbers and false
otherwise. The
Find
method uses this predicate to locate the first matching element.
Named Predicate Methods
You can also use named methods as predicates. This is useful when the logic is complex or reused in multiple places.
bool IsEven(int number)
{
return number % 2 == 0;
}
int result = numbers.Find(IsEven);
Console.WriteLine(result); // Output: 4
The method IsEven
matches the Predicate<int>
signature, so it can be passed directly to Find
. This improves readability and makes your code easier to test.
Removing Items with Predicate
You can use predicates to remove items from a list based on a condition. For example, to remove all negative numbers:
List<int> values = new List<int> { -3, 0, 5, -1, 8 };
values.RemoveAll(v => v < 0);
Console.WriteLine(string.Join(", ", values)); // Output: 0, 5, 8
The RemoveAll
method uses the predicate to test each element. If the predicate
returns true
, the element is removed.
Predicate with Custom Types
Predicate delegates work with any type - not just primitives. Suppose you have a list of products and want to find one that’s out of stock:
class Product
{
public string Name { get; set; }
public int Quantity { get; set; }
}
List<Product> products = new List<Product>
{
new Product { Name = "Pen", Quantity = 10 },
new Product { Name = "Notebook", Quantity = 0 },
new Product { Name = "Eraser", Quantity = 5 }
};
Product outOfStock = products.Find(p => p.Quantity == 0);
Console.WriteLine(outOfStock.Name); // Output: Notebook
The lambda p => p.Quantity == 0
is a predicate that checks for zero
quantity. This pattern is common in business logic and filtering scenarios.
Predicate vs Func<T, bool>
You might wonder - what’s the difference between Predicate<T>
and Func<T, bool>
? Functionally, they’re identical. Both represent a method
that takes a T
and returns a bool
.
The main difference is semantic. Predicate<T>
is used when the intent is
to test a condition. Func<T, bool>
is more general-purpose. Some APIs
prefer one over the other - for example, List<T>.Find
uses Predicate<T>
, while LINQ’s Where
uses
Func<T, bool>
.
You can use lambdas interchangeably with both Predicate<T>
and Func<T, bool>
.
The compiler infers the correct delegate type based on context.
Combining Predicates
You can combine predicates to build more complex conditions. For example, to find products that are out of stock and have a name longer than 5 characters:
Predicate<Product> isOutOfStock = p => p.Quantity == 0;
Predicate<Product> nameTooLong = p => p.Name.Length > 5;
Predicate<Product> combined = p => isOutOfStock(p) && nameTooLong(p);
Product result = products.Find(combined);
Console.WriteLine(result?.Name); // Output: Notebook
This pattern is useful for building dynamic filters - especially when predicates are generated at runtime or composed from user input.
Predicate in LINQ Queries
LINQ methods like Where
, Any
, and All
use Func<T, bool>
- which behaves like
a predicate. You can use the same logic in LINQ queries:
bool hasOutOfStock = products.Any(p => p.Quantity == 0);
bool allAvailable = products.All(p => p.Quantity > 0);
These methods test conditions across the collection - returning true
or false
based on the predicate.
Predicate for Validation
You can use predicates for validation - checking whether input meets certain criteria. For example:
Predicate<string> isValidEmail = email => email.Contains("@") && email.EndsWith(".com");
Console.WriteLine(isValidEmail("[email protected]")); // Output: True
Console.WriteLine(isValidEmail("invalid-email")); // Output: False
This pattern is useful for form validation, input sanitization, and rule enforcement.
Predicate with Closures
You can use closures to customize predicates based on external variables. For example, filtering numbers greater than a threshold:
Predicate<int> GreaterThan(int threshold)
{
return x => x > threshold;
}
var isGreaterThanFive = GreaterThan(5);
Console.WriteLine(isGreaterThanFive(7)); // Output: True
The returned predicate captures the threshold
variable - allowing you to
generate dynamic conditions.
Summary
Predicate delegates are a powerful tool for testing conditions, filtering collections, and building decision
logic. In this article, you learned how to use Predicate<T>
with lists,
custom types, and LINQ queries. You explored how to write named and anonymous predicates, combine them, and use
closures to customize behavior.
Predicate delegates help you write cleaner, more expressive code - especially when working with collections, validation, and functional programming patterns. They are a natural fit for higher-order functions and a key part of C#’s functional toolbox.
In the next article, we’ll explore the Observer Pattern - a design pattern that builds on events and delegates to create reactive systems where objects can subscribe to changes and respond dynamically.