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:
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:
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:
- We defined a
Predicate<int>
calledisEven
that returnstrue
if a number is divisible by 2 - We used the
List.FindAll()
method which accepts a predicate delegate - 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)
Predicate<int> isPositive = x => x > 0;
2. Anonymous Methods
Predicate<string> isLongString = delegate(string s) { return s.Length > 10; };
3. Named Methods
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:
// 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
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
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:
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:
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()
, andExists()
- 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
- Create a list of strings and use a predicate to filter out all strings that are shorter than 5 characters.
- Create a
Student
class with properties forName
,Age
, andGrade
. Use predicates to find:- Students who are eligible for honors (Grade > 90)
- Students who need tutoring (Grade < 70)
- Write a method that takes a list of integers and two predicates. Return all numbers that satisfy both predicates.
- 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! :)