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.