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
- Delegates: Type-safe references to methods.
- Multicast Delegates: Can point to multiple methods.
- Events: Mechanism for publishers to notify subscribers of occurrences.
- Publisher-Subscriber Pattern: Events facilitate loose coupling between objects.
- EventArgs: Standard way to pass event-related data.
- EventHandler<TEventArgs>: Generic delegate for common event patterns.