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 [email protected]. I’d greatly appreciate your feedback! :)