How to Create Generic Delegates in C

Generic delegates in C# allow you to define type-safe callback functions and method references without locking them to a single data type. By using type parameters, generic delegates provide maximum reusability and efficiency across different parts of your application. This guide covers how to define custom generic delegates, apply type constraints to them, and leverage built-in generic delegates such as Action and Func.

Defining a Custom Generic Delegate

To create a generic delegate, use the delegate keyword followed by the return type, the delegate name, and angle brackets < > containing one or more type parameters.

// Definition of a generic delegate
public delegate TResult Transformer<TInput, TResult>(TInput input);

class Program
{
    static void Main()
    {
        // Instantiate the delegate with specific types (string, int)
        Transformer<string, int> stringLengthCalculator = CountCharacters;
        
        int length = stringLengthCalculator("Hello World");
        Console.WriteLine($"Character count: {length}"); // Output: 11
        
        // Reuse the same delegate definition with different types (double, double)
        Transformer<double, double> squareCalculator = Square;
        
        double result = squareCalculator(5.0);
        Console.WriteLine($"Square result: {result}"); // Output: 25
    }

    static int CountCharacters(string text) => text.Length;
    static double Square(double number) => number * number;
}

Adding Generic Constraints

You can restrict the types that can be supplied to your generic delegate by using generic constraints with the where keyword. This ensures compile-time validation for required base classes, interfaces, or constructors.

public delegate void EntityHandler<T>(T entity) where T : class, IComparable<T>;

class Program
{
    static void ProcessItem(string item)
    {
        Console.WriteLine($"Processing: {item}");
    }

    static void Main()
    {
        // Valid because string is a class and implements IComparable<string>
        EntityHandler<string> handler = ProcessItem;
        handler("Valid Entity");
    }
}

Using Built-in Generic Delegates

In modern C#, creating custom delegates is often unnecessary because the .NET framework provides flexible built-in generic delegates:

  1. Action<...>: Used for methods that return void and accept from 0 to 16 parameters.

    Action<string, int> printDetails = (name, age) => 
        Console.WriteLine($"{name} is {age} years old.");
    
    printDetails("Alice", 30);
  2. Func<..., TResult>: Used for methods that return a value and accept from 0 to 16 parameters (the last type parameter denotes the return type).

    Func<int, int, int> add = (a, b) => a + b;
    
    int sum = add(10, 20); // sum is 30
  3. Predicate<T>: Used specifically for methods that evaluate an input against a condition and return a bool.

    Predicate<int> isEven = x => x % 2 == 0;
    
    bool check = isEven(4); // check is true

Custom generic delegates should primarily be declared when you need custom parameter attributes, specific variance keywords (in for contravariance, out for covariance), or distinct descriptive naming beyond what Action and Func offer.