C# Delegates and Events

Delegates and events are fundamental concepts in C# for implementing publish-subscribe patterns and enabling communication between objects in a decoupled manner. They are crucial for building responsive user interfaces, handling asynchronous operations, and creating flexible application architectures.

Delegates

A delegate is a type that represents references to methods with a particular parameter list and return type. Think of a delegate as a type-safe function pointer. It defines the signature of the methods it can refer to.

Declaring a Delegate

You declare a delegate using the delegate keyword:


public delegate void MyDelegate(string message);
            

This declares a delegate type named MyDelegate that can refer to any method that takes a single string argument and returns void.

Instantiating a Delegate

You can instantiate a delegate by assigning a method with a matching signature to it:


public class MyClass
{
    public void PrintMessage(string msg)
    {
        Console.WriteLine($"Message: {msg}");
    }

    public void ExecuteDelegate()
    {
        // Instantiate the delegate
        MyDelegate handler = new MyDelegate(PrintMessage);
        // Or using a shorthand syntax:
        // MyDelegate handler = PrintMessage;

        // Invoke the delegate
        handler("Hello, Delegates!");
    }
}
            

Multicast Delegates

Delegates can refer to more than one method. This is known as a multicast delegate. You can add methods to the delegate invocation list using the + operator and remove them using the - operator.


public delegate void MyMultiDelegate(int value);

public class Calculator
{
    public void Add(int x) { Console.WriteLine($"Add: {x}"); }
    public void Subtract(int x) { Console.WriteLine($"Subtract: {x}"); }
}

public class MultiDelegateExample
{
    public void Run()
    {
        MyMultiDelegate operations = null;
        Calculator calc = new Calculator();

        operations += calc.Add;       // Add method
        operations += calc.Subtract;  // Add another method

        operations(10); // Both Add and Subtract methods will be called
        // Output:
        // Add: 10
        // Subtract: 10

        operations -= calc.Subtract; // Remove Subtract method

        operations(20); // Only Add method will be called
        // Output:
        // Add: 20
    }
}
            

Events

Events are a way for a class to notify other classes when something happens. The class that raises the event is called the publisher, and the classes that listen for the event are called subscribers. Events are built upon delegates.

Declaring an Event

An event is declared using the event keyword, typically associated with a delegate type. The standard convention for event delegates is to have a void return type and take two arguments: an object sender (the object that raised the event) and a derived type of EventArgs (containing event data).


// Define a class to hold event data
public class MyEventArgs : EventArgs
{
    public string Message { get; }
    public MyEventArgs(string message)
    {
        Message = message;
    }
}

// Define the delegate type for the event
public delegate void MyEventHandler(object sender, MyEventArgs e);

// Class that raises the event
public class Publisher
{
    // Declare the event
    public event MyEventHandler MyCustomEvent;

    public void RaiseEvent(string msg)
    {
        Console.WriteLine("Publisher is raising an event...");
        // Create event arguments
        MyEventArgs args = new MyEventArgs(msg);
        // Invoke the event (check if there are any subscribers)
        OnMyCustomEvent(args);
    }

    // Protected virtual method to raise the event, following convention
    protected virtual void OnMyCustomEvent(MyEventArgs e)
    {
        // Make a temporary copy of the event to avoid race conditions
        // if a subscriber unsubscribes unexpectedly
        MyEventHandler handler = MyCustomEvent;
        if (handler != null)
        {
            handler(this, e); // 'this' refers to the Publisher instance
        }
    }
}
            

Subscribing to an Event

A subscriber subscribes to an event using the + operator on the event:


public class Subscriber
{
    public void SubscribeToPublisher(Publisher pub)
    {
        // Subscribe to the event
        pub.MyCustomEvent += HandleMyCustomEvent;
    }

    public void UnsubscribeFromPublisher(Publisher pub)
    {
        // Unsubscribe from the event
        pub.MyCustomEvent -= HandleMyCustomEvent;
    }

    // The event handler method must match the delegate signature
    private void HandleMyCustomEvent(object sender, MyEventArgs e)
    {
        Console.WriteLine($"Subscriber received event from {sender.GetType().Name} with message: {e.Message}");
    }
}
            

Using Events

Here's how you would put it all together:

Example Usage


public class Program
{
    public static void Main(string[] args)
    {
        Publisher publisher = new Publisher();
        Subscriber subscriber = new Subscriber();

        // Subscriber attaches to the event
        subscriber.SubscribeToPublisher(publisher);

        // Publisher raises an event
        publisher.RaiseEvent("This is a notification!");

        // Subscriber detaches from the event
        subscriber.UnsubscribeFromPublisher(publisher);

        // Publisher raises another event (subscriber won't receive this)
        publisher.RaiseEvent("This event will not be received.");
    }
}
                

Expected Output:


Publisher is raising an event...
Subscriber received event from Publisher with message: This is a notification!
Publisher is raising an event...
                

Built-in Event Accessors (C# 3.0 and later)

C# 3.0 introduced simplified syntax for event accessors, making event declaration more concise:


public class ConcisePublisher
{
    // Simplified event declaration using generic EventHandler delegate
    public event EventHandler<MyEventArgs> MySimplifiedEvent;

    public void RaiseSimplifiedEvent(string msg)
    {
        // Invoking the event directly
        MySimplifiedEvent?.Invoke(this, new MyEventArgs(msg));
    }
}
            

The ?. (null-conditional operator) safely invokes the event only if there are subscribers.

Key Takeaways