C# - Predicate Delegate
A Predicate<T>
delegate represents a method that defines a set of criteria and determines whether the passed parameter meets those criteria. A method used with this delegate must take a single input parameter and return a boolean value (true
or false
). It's mainly used to test an item to see if it meets some criteria.
The Predicate<T>
delegate is frequently used with collection classes, like List<T>
, to find items or check if items exist that meet a specific condition.
1. Predicate Delegate Example:
Let's say you have a list of integers, and you want to find numbers that are even. You can use a Predicate<int>
delegate to represent the method that checks if a number is even.
using System;
using System.Collections.Generic;
namespace PredicateDelegateExample
{
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Using Predicate delegate to find all even numbers in the list.
List<int> evenNumbers = numbers.FindAll(IsEven);
Console.WriteLine("Even Numbers:");
foreach (var number in evenNumbers)
{
Console.WriteLine(number);
}
}
// Method that matches the Predicate<int> delegate.
static bool IsEven(int number)
{
return number % 2 == 0;
}
}
}
The output of the program will be:
Even Numbers:
2
4
6
8
10
In the above example:
- We defined a method
IsEven
that checks if a number is even.
- We used the
List<T>.FindAll
method, which accepts a Predicate<T>
delegate, to retrieve all even numbers from the list.
- The
IsEven
method is passed to FindAll
as the Predicate<int>
delegate for filtering the list.
With the introduction of lambda expressions in C# 3.0, the example can be simplified using an inline lambda:
List<int> evenNumbers = numbers.FindAll(n => n % 2 == 0);
This approach is more concise and is often used for short filtering or testing functions.
2. When to Use Predicate<T> Delegate
When to use:
- Filtering Collections: When filtering items in collections based on criteria,
Predicate<T>
shines. Methods like List<T>.FindAll
and List<T>.Exists
use this delegate.
This C# example demonstrates how to use the Predicate<T>
delegate to filter a collection of integers.
using System;
using System.Collections.Generic;
namespace PredicateDelegateFilteringExample
{
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Using Predicate delegate to find all even numbers in the list
List<int> evenNumbers = numbers.FindAll(IsEven);
// Using Predicate delegate to find all numbers greater than 5
List<int> greaterThanFive = numbers.FindAll(IsGreaterThanFive);
Console.WriteLine("Even Numbers:");
foreach (var number in evenNumbers)
{
Console.WriteLine(number);
}
Console.WriteLine("\nNumbers Greater Than 5:");
foreach (var number in greaterThanFive)
{
Console.WriteLine(number);
}
}
// Method that matches the Predicate<int> delegate (for even numbers)
static bool IsEven(int number)
{
return number % 2 == 0;
}
// Method that matches the Predicate<int> delegate (for numbers greater than 5)
static bool IsGreaterThanFive(int number)
{
return number > 5;
}
}
}
Expected Output
Even Numbers:
2
4
6
8
10
Numbers Greater Than 5:
6
7
8
9
10
- Searching Elements: For finding specific elements in collections based on a condition,
Predicate<T>
is suitable.
This C# example demonstrates how to use the Predicate<T>
delegate to search for specific elements in a list of integers.
using System;
using System.Collections.Generic;
namespace PredicateDelegateSearchingExample
{
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Using Predicate delegate to find the first even number greater than 5
int firstEvenGreaterThanFive = numbers.Find(x => IsEven(x) && x > 5);
// Using Predicate delegate to find the first odd number
int firstOdd = numbers.Find(IsOdd);
Console.WriteLine($"First even number greater than 5: {firstEvenGreaterThanFive}");
Console.WriteLine($"First odd number: {firstOdd}");
}
// Method to check if a number is even
static bool IsEven(int number)
{
return number % 2 == 0;
}
// Method to check if a number is odd
static bool IsOdd(int number)
{
return number % 2 != 0;
}
}
}
Expected Output
First even number greater than 5: 6
First odd number: 1
Validation: Define methods that require validation functions using Predicate<T>
to represent the function.
This C# example demonstrates how to use the Predicate<T>
delegate for validation purposes. It validates a list of email addresses to check whether they are valid.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace PredicateDelegateValidationExample
{
class Program
{
static void Main()
{
List<string> emails = new List<string>
{
"john.doe@example.com",
"jane@",
"random.text",
"joe.bloggs@site.net"
};
// Using Predicate delegate to find all valid email addresses
List<string> validEmails = emails.FindAll(IsValidEmail);
Console.WriteLine("Valid Email Addresses:");
foreach (var email in validEmails)
{
Console.WriteLine(email);
}
}
// Method that matches the Predicate<string> delegate (for email validation)
static bool IsValidEmail(string email)
{
string pattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
return Regex.IsMatch(email, pattern);
}
}
}
Expected Output
Valid Email Addresses:
john.doe@example.com
joe.bloggs@site.net
Readability: In scenarios where evaluating a condition on data is the main intent, Predicate<T>
improves code clarity.
When to Avoid:
- Returning Data: For functions returning non-boolean data, prefer other delegate types like
Func<T, TResult>
.
- Multiple Parameters: If evaluating conditions based on multiple parameters, consider
Func<T1, T2, ..., bool>
or custom delegates.
- Event Handling: For events, use the
EventHandler
delegate or custom ones.
- General Actions: For methods performing actions without returning values,
Action
or Action<T>
delegates are more appropriate.
- Overhead: Delegates introduce some overhead. In performance-critical scenarios, inlining the condition may be more efficient than using a delegate.
Choosing to use Predicate<T>
, another delegate, or no delegate depends on your code's specific needs, emphasizing clarity, maintainability, and performance.
3. Predicate<T> Delegate Real-time Example
Scenario:
In real-world applications, Predicate<T>
delegates can be especially useful for filtering and validating data sets, among other uses. Below is a simplified C# example that mimics a real-world scenario involving a list of products in a shopping cart. The program uses Predicate<T>
to filter the list based on the category of the products.
using System;
using System.Collections.Generic;
namespace PredicateRealWorldExample
{
class Program
{
static void Main()
{
List<Product> products = new List<Product>
{
new Product { Name = "Laptop", Category = "Electronics", Price = 1000 },
new Product { Name = "T-Shirt", Category = "Apparel", Price = 20 },
new Product { Name = "Coffee Maker", Category = "Home Appliances", Price = 50 },
new Product { Name = "Mobile Phone", Category = "Electronics", Price = 500 }
};
// Using Predicate delegate to find all products in the "Electronics" category
List<Product> electronics = products.FindAll(IsElectronicsCategory);
Console.WriteLine("Electronics Products:");
foreach (var product in electronics)
{
Console.WriteLine($"{product.Name} - ${product.Price}");
}
}
// Method to match the Predicate<Product> delegate
static bool IsElectronicsCategory(Product product)
{
return product.Category == "Electronics";
}
}
class Product
{
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
}
Execution Flow:
- Initialize List: When the program starts, a list of
Product
objects named products
is created with some example items.
- Predicate Definition: The
IsElectronicsCategory
method is defined to serve as our predicate function for filtering.
- Use of
FindAll
: The FindAll
method on the List<T>
class is then called, passing in the IsElectronicsCategory
method as a Predicate<Product>
delegate. This method scans through each element in the products
list and applies the IsElectronicsCategory
function to it.
- Filtered List: The filtered list, named
electronics
, contains only the products that satisfy the condition set in IsElectronicsCategory
, i.e., products in the "Electronics" category.
- Output: The program then iterates over the
electronics
list and prints out the names and prices of the filtered products.
Expected Output:
Electronics Products:
Laptop - $1000
Mobile Phone - $500
- Definition:
Predicate<T>
represents a method defining a set of criteria and determines if the passed parameter meets these criteria, returning a boolean.
- Single Parameter: It takes a single parameter of type
T
and returns a bool
.
- Usage with Collections: Commonly used with collections, especially methods in
List<T>
like Find
, FindAll
, Exists
, and RemoveAll
.
- Lambda Expressions: From C# 3.0 onwards, lambda expressions can be used to create
Predicate<T>
instances inline.
- Purpose: Primarily for filtering, finding, or validating data. Use it to determine if an item matches criteria or validate an input.
- Alternative to Other Delegates: For operations where both data processing and condition determination are needed,
Func<T, bool>
is an alternative. For checking conditions, Predicate<T>
clarifies intent.
- Performance: There's potential overhead with delegate invocation. In performance-sensitive situations, consider other methods.
- Not for Events:
Predicate<T>
isn't for event handling. Use delegates like EventHandler
or custom ones for events.
- Boolean Result: It should always return a boolean. For other return types, consider
Func<T, TResult>
.
- Flexibility: An advantage of
Predicate<T>
is flexibility. You can define behavior (the method the delegate points to) at runtime.