Skip to main content

C# Predicate Delegates

Introduction​

In C#, a Predicate delegate is a specialized delegate type that represents a method that takes a single input parameter and returns a boolean value (true or false). Predicates are commonly used to filter collections, validate data, and implement search functionality. They are particularly useful when working with LINQ and built-in array/collection methods that require conditional logic.

The Predicate delegate is defined in the System namespace with the following signature:

csharp
public delegate bool Predicate<in T>(T obj);

This means a Predicate:

  • Takes one parameter of type T
  • Returns a bool value
  • Is generic, allowing it to work with any data type

Basic Syntax and Usage​

Let's start with a simple example of defining and using a Predicate delegate:

csharp
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
// Create a list of integers
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Create a predicate that checks if a number is even
Predicate<int> isEven = x => x % 2 == 0;

// Use the predicate with List.FindAll method
List<int> evenNumbers = numbers.FindAll(isEven);

// Display the results
Console.WriteLine("Even numbers:");
foreach (int num in evenNumbers)
{
Console.WriteLine(num);
}
}
}

Output:

Even numbers:
2
4
6
8
10

In this example:

  1. We defined a Predicate<int> called isEven that returns true if a number is divisible by 2
  2. We used the List.FindAll() method which accepts a predicate delegate
  3. The method returns all elements in the list that match the predicate condition

Different Ways to Define Predicates​

There are multiple ways to define a predicate in C#:

1. Lambda Expressions (Most Common)​

csharp
Predicate<int> isPositive = x => x > 0;

2. Anonymous Methods​

csharp
Predicate<string> isLongString = delegate(string s) { return s.Length > 10; };

3. Named Methods​

csharp
static bool IsAdult(Person person)
{
return person.Age >= 18;
}

// Later in your code
Predicate<Person> adultFilter = IsAdult;

Predicate vs. Func Delegate​

You might wonder about the difference between a Predicate<T> and a Func<T, bool>, as they appear similar:

csharp
// These seem equivalent:
Predicate<int> predicate = x => x > 5;
Func<int, bool> func = x => x > 5;

The main differences are:

  • Predicate<T> is specifically designed for returning boolean results
  • Many collection methods in .NET specifically take Predicate<T> parameters
  • Func<T, bool> is more general and can be used in a wider variety of contexts

While they're functionally similar, using the appropriate delegate type makes your code more readable and semantically correct.

Common Methods That Use Predicates​

The .NET Framework includes several collection methods that accept predicates:

List<T> Methods​

csharp
List<string> names = new List<string> { "Alice", "Bob", "Charlie", "David", "Eve" };

// Find the first element that matches a condition
string firstLongName = names.Find(name => name.Length > 5); // Returns "Charlie"

// Find all elements that match a condition
List<string> longNames = names.FindAll(name => name.Length > 3);

// Check if any element matches a condition
bool hasShortName = names.Exists(name => name.Length < 4); // Returns true (Bob, Eve)

// Remove all elements that match a condition
names.RemoveAll(name => name.StartsWith("A")); // Removes "Alice"

Array Methods​

csharp
int[] numbers = { 10, 20, 30, 40, 50 };

// Find the first match
int firstMatch = Array.Find(numbers, n => n > 25); // Returns 30

// Find all matches
int[] matches = Array.FindAll(numbers, n => n > 25); // Returns { 30, 40, 50 }

// Check if any element satisfies the condition
bool anyMatch = Array.Exists(numbers, n => n > 100); // Returns false

// Find the index of first match
int index = Array.FindIndex(numbers, n => n == 30); // Returns 2

Practical Example: Custom Search Feature​

Let's build a more comprehensive example of using predicates to create a flexible search feature:

csharp
using System;
using System.Collections.Generic;

class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }

public override string ToString()
{
return $"#{Id}: {Name} (${Price}) - {Category}";
}
}

class ProductCatalog
{
private List<Product> products;

public ProductCatalog()
{
products = new List<Product>
{
new Product { Id = 1, Name = "Laptop", Price = 1200.00m, Category = "Electronics" },
new Product { Id = 2, Name = "Smartphone", Price = 800.00m, Category = "Electronics" },
new Product { Id = 3, Name = "Coffee Mug", Price = 12.99m, Category = "Kitchen" },
new Product { Id = 4, Name = "Headphones", Price = 100.00m, Category = "Electronics" },
new Product { Id = 5, Name = "Novel Book", Price = 15.50m, Category = "Books" }
};
}

public List<Product> Search(Predicate<Product> criteria)
{
return products.FindAll(criteria);
}

// Predefined search criteria
public static class SearchCriteria
{
public static Predicate<Product> ByCategory(string category)
{
return p => p.Category.Equals(category, StringComparison.OrdinalIgnoreCase);
}

public static Predicate<Product> ByPriceRange(decimal min, decimal max)
{
return p => p.Price >= min && p.Price <= max;
}

public static Predicate<Product> ByNameContains(string text)
{
return p => p.Name.Contains(text, StringComparison.OrdinalIgnoreCase);
}
}
}

class Program
{
static void Main()
{
ProductCatalog catalog = new ProductCatalog();

// Search for electronics
var electronics = catalog.Search(ProductCatalog.SearchCriteria.ByCategory("Electronics"));
DisplayResults("Electronics:", electronics);

// Search for products between $10 and $100
var affordableItems = catalog.Search(ProductCatalog.SearchCriteria.ByPriceRange(10, 100));
DisplayResults("Affordable Items ($10-$100):", affordableItems);

// Search for products with "phone" in the name
var phones = catalog.Search(ProductCatalog.SearchCriteria.ByNameContains("phone"));
DisplayResults("Products with 'phone' in name:", phones);

// Combining predicates
Predicate<Product> combinedCriteria = p =>
p.Category == "Electronics" && p.Price < 150;
var cheapElectronics = catalog.Search(combinedCriteria);
DisplayResults("Cheap Electronics:", cheapElectronics);
}

static void DisplayResults(string title, List<Product> products)
{
Console.WriteLine(title);
foreach (var product in products)
{
Console.WriteLine($" {product}");
}
Console.WriteLine();
}
}

Output:

Electronics:
#1: Laptop ($1200.00) - Electronics
#2: Smartphone ($800.00) - Electronics
#4: Headphones ($100.00) - Electronics

Affordable Items ($10-$100):
#3: Coffee Mug ($12.99) - Kitchen
#4: Headphones ($100.00) - Electronics
#5: Novel Book ($15.50) - Books

Products with 'phone' in name:
#2: Smartphone ($800.00) - Electronics
#4: Headphones ($100.00) - Electronics

Cheap Electronics:
#4: Headphones ($100.00) - Electronics

This example demonstrates how predicates can be used to create a flexible search system for a product catalog.

Combining Predicates​

You can combine predicates to create more complex conditions:

csharp
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Individual predicates
Predicate<int> isEven = n => n % 2 == 0;
Predicate<int> greaterThanFive = n => n > 5;

// Combining predicates
Predicate<int> isEvenAndGreaterThanFive = n => isEven(n) && greaterThanFive(n);

// Using the combined predicate
List<int> result = numbers.FindAll(isEvenAndGreaterThanFive);

Console.WriteLine("Even numbers greater than 5:");
foreach (var number in result)
{
Console.WriteLine(number);
}
}
}

Output:

Even numbers greater than 5:
6
8
10

Summary​

Predicate delegates in C# are a powerful tool for working with conditional logic, especially when filtering collections. Key points to remember:

  • A Predicate<T> is a delegate that returns a boolean value based on a single input parameter
  • Predicates are commonly used with collection methods like FindAll(), Find(), and Exists()
  • You can define predicates using lambda expressions, anonymous methods, or named methods
  • Predicates can be combined to create complex filtering conditions
  • They play a central role in LINQ operations and in creating flexible search functionality

Exercises​

  1. Create a list of strings and use a predicate to filter out all strings that are shorter than 5 characters.
  2. Create a Student class with properties for Name, Age, and Grade. Use predicates to find:
    • Students who are eligible for honors (Grade > 90)
    • Students who need tutoring (Grade < 70)
  3. Write a method that takes a list of integers and two predicates. Return all numbers that satisfy both predicates.
  4. Create a custom collection class with a method that accepts multiple predicates and returns elements that match ANY of the predicates (logical OR).

Additional Resources​



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)