C# Delegates: A Practical Example

Delegates are a fundamental concept in C# that provide a type-safe way to represent methods. They are often used to implement event handling, callback mechanisms, and asynchronous operations. This example demonstrates how to define, instantiate, and use delegates in C#.

Scenario: A Simple Calculator

Let's imagine we're building a simple calculator that can perform basic arithmetic operations. We can use a delegate to represent the operation itself, allowing us to pass different operations to a method that executes them.

1. Define the Delegate Type

First, we define a delegate type that matches the signature of the methods we want to represent. Our calculator methods will take two integers and return an integer. The delegate declaration uses the delegate keyword.

public delegate int ArithmeticOperation(int a, int b);
            

2. Define the Methods to Delegate

Next, we create the actual methods that will perform the calculations. These methods must have a signature that matches the delegate type.

public class Calculator
{
    public static int Add(int x, int y)
    {
        return x + y;
    }

    public static int Subtract(int x, int y)
    {
        return x - y;
    }

    public static int Multiply(int x, int y)
    {
        return x * y;
    }

    public void ExecuteOperation(
        int operand1,
        int operand2,
        ArithmeticOperation operation)
    {
        Console.WriteLine("Executing operation...");
        int result = operation(operand1, operand2);
        Console.WriteLine($"Result: {result}");
    }
}
            

3. Using the Delegate

Now, in our main program logic, we can create instances of the delegate and pass them to the ExecuteOperation method.

public class Program
{
    public static void Main(string[] args)
    {
        Calculator calc = new Calculator();

        ArithmeticOperation addDelegate = Calculator.Add;
        ArithmeticOperation subtractDelegate = Calculator.Subtract;
        ArithmeticOperation multiplyDelegate = Calculator.Multiply;

        Console.WriteLine("--- Performing Addition ---");
        calc.ExecuteOperation(10, 5, addDelegate);

        Console.WriteLine("\n--- Performing Subtraction ---");
        calc.ExecuteOperation(20, 7, subtractDelegate);

        Console.WriteLine("\n--- Performing Multiplication ---");
        calc.ExecuteOperation(6, 8, multiplyDelegate);
    }
}
            

Example Output:

--- Performing Addition ---
Executing operation...
Result: 15

--- Performing Subtraction ---
Executing operation...
Result: 13

--- Performing Multiplication ---
Executing operation...
Result: 48
                

Key Takeaways

  • Delegates provide type safety for method references.
  • They enable passing methods as arguments to other methods.
  • The delegate type's signature must match the method signatures it refers to.
  • You can assign a method to a delegate variable, or pass a method directly when creating a delegate instance.

Modern C# Syntax (Expression-bodied members & Lambda Expressions)

C# offers more concise ways to achieve this using expression-bodied members and lambda expressions.

public delegate int ArithmeticOperation(int a, int b);

public class ModernCalculator
{
    public static int Add(int x, int y) => x + y;
    public static int Subtract(int x, int y) => x - y;

    public void ExecuteOperation(
        int operand1,
        int operand2,
        ArithmeticOperation operation)
    {
        int result = operation(operand1, operand2);
        Console.WriteLine($"Modern Result: {result}");
    }
}

public class ModernProgram
{
    public static void Main(string[] args)
    {
        ModernCalculator modernCalc = new ModernCalculator();

        modernCalc.ExecuteOperation(50, 10, ModernCalculator.Add);
        modernCalc.ExecuteOperation(30, 5, ModernCalculator.Subtract);

        // Using lambda expressions directly
        modernCalc.ExecuteOperation(7, 7, (a, b) => a * b);
    }
}
            

Modern Example Output:

Modern Result: 60
Modern Result: 25
Modern Result: 49
                

LINQ and Delegates

Delegates are heavily used in conjunction with LINQ (Language Integrated Query). For instance, methods like Where and Select in LINQ accept delegates (often in the form of lambda expressions) to define filtering and transformation logic.

This example illustrates the core functionality of delegates in C#. Their ability to represent methods as objects unlocks powerful programming patterns, especially in event-driven and functional programming paradigms.