Delegates in C#

A delegate in C# is a type that represents references to methods with a particular parameter list and return type. Delegates are close to C++ function pointers but are object-oriented, type-safe, and secure.

Why Do I Need Delegates?

Delegates allow methods to be passed as parameters, enabling flexible designs and dynamic behavior. They are useful for:

  • Evident designation of callback methods for operations like sort/filter.
  • Event handling in applications (delegates are required for event-driven programming).
  • Encapsulation of method references allowing execution of methods at runtime.

Key Concepts of Delegates

Declaration: Declare the delegate type with a return type and parameters.

Instantiation: Create an instance of the delegate, which points to a specific method.

Invocation: Invoke the method through the delegate.

Multicasting: A delegate can point to multiple methods.

Types of Delegates in C#

  • Single-Cast Delegates: Points to a single method.
  • Multi-Cast Delegates: Points to multiple methods.
  • Generic Delegates:
    • Func: Used when the method returns a value.
    • Action: Used for methods that do not return a value.
    • Predicate: Used for methods that return a boolean.

Step-by-Step Tutorial

1. Declaring a Delegate

To declare a delegate, use the delegate keyword followed by the signature of the method.

public delegate int MyDelegate(int x, int y);
    

In the above example, MyDelegate can point to any method with two int parameters and returns an int.

2. Delegate Creation and Assignment

Declare and instantiate the delegate, then assign a target method to it.

public class Operations
{
    public int Add(int a, int b) => a + b;
    public int Multiply(int a, int b) => a * b;
}
class Program
{
    static void Main()
    {
        Operations operations = new Operations();
        MyDelegate del = new MyDelegate(operations.Add);

        int result = del(10, 20);
        Console.WriteLine(result);  // Output: 30
    }
}
    

3. Multicasting Delegates

A multicast delegate can refer to more than one method. Use += to attach methods and -= to detach them.

MyDelegate del = operations.Add;
del += operations.Multiply;
int result = del(10, 20);
Console.WriteLine(result);  // Output may vary depending on method resolution order.
    

4. Using Predefined Generic Delegates

Func: Delegate that returns a value.

Func add = (a, b) => a + b;
int sum = add(5, 10);  // Output: 15
    

Action: Does not return a value.

Action greet = name => Console.WriteLine("Hello, " + name);
greet("World");  // Output: Hello, World
    

Predicate: Returns a boolean value.

Predicate isPositive = x => x > 0;
bool result = isPositive(10);  // Output: true
    

5. Delegates with Events

Events in C# use delegates to handle notifications in event-driven programs.

public class Publisher
{
    public delegate void Notify();
    public event Notify OnPublish;

    public void Publish()
    {
        Console.WriteLine("Publishing an event.");
        OnPublish?.Invoke();
    }
}
public class Subscriber
{
    public void OnNotified()
    {
        Console.WriteLine("Subscriber notified!");
    }
}
class Program
{
    static void Main()
    {
        Publisher pub = new Publisher();
        Subscriber sub = new Subscriber();

        pub.OnPublish += sub.OnNotified;

        pub.Publish();
        // Output:
        // Publishing an event.
        // Subscriber notified!
    }
}
    

Delegate Real-World Use Cases

  • Logging: Pass different logging methods (file log, console log, etc.) as delegates.
  • Button Click Handlers: Use delegates to direct button clicks or other user actions.
  • Task Scheduling: Schedule separate operations using delegates for each operation.
  • Data Processing: Use delegates to specify distinct strategies for data processing.

Benefits of Delegates

Loose Coupling: Offers flexibility and modularity.

Event Handling: Essential for implementing events and subscribers.

Multithreading: Methods like BeginInvoke facilitate asynchronous processing.

Important Points to Remember

  • Delegates must match the signature of the methods they reference.
  • Generic delegates (Func, Action, Predicate) simplify delegate usage.
  • Delegates are reference types and can be null unless assigned a method.