Differences Between Delegates and Events in C#

In C#, delegates and events are closely related but serve different purposes. If you're new to these concepts, it’s recommended to first read the dedicated pages on Delegates and Events for a detailed explanation. This page focuses on the key differences between them and when to use one over the other.

Key Differences Between Delegates and Events

Aspect Delegates Events
Definition A delegate is a type-safe function pointer that references a method. An event is a wrapper around a delegate, providing a secure way to implement the publisher-subscriber pattern.
Primary Purpose Used to reference and invoke methods directly. Used to notify subscribers when something happens (observer pattern).
Multicast Support Supports multicast; can hold references to multiple methods. Uses delegates internally but provides controlled subscription mechanisms.
Invocation Control Methods referenced by delegates can be invoked directly from outside the class. Events can only be invoked by the class that declares them (encapsulation).
Subscription Semantics No built-in support for subscription/unsubscription. Provides clear semantics for subscription (+=) and unsubscription (-=).
Use Case Ideal for callback mechanisms, dynamic method invocation, and multicast. Ideal for implementing event-driven programming and the observer pattern.

When to Use Delegates

Delegates are like method containers. They allow you to store and call methods dynamically. Think of them as a way to say, "Hey, I’ll call this method later when I need it." Here are some common scenarios where delegates are useful:

1. Callbacks

Imagine you’re asking someone to do a task and want them to notify you when it’s done. Delegates are perfect for this. For example, you can pass a method as a parameter to another method and call it when the task is complete.

public delegate void NotifyUser(string message);

public void ProcessData(NotifyUser callback)
{
// Do some work...
callback("Task completed!");
}

public void ShowMessage(string message)
{
Console.WriteLine(message);
}

// Usage
ProcessData(ShowMessage); // Pass the method as a callback

2. Dynamic Method Invocation

If you don’t know which method to call at compile time, delegates let you decide at runtime. For example, you can choose between different methods based on user input or other conditions.

public delegate void MathOperation(int a, int b);

public void Add(int a, int b) => Console.WriteLine(a + b);
public void Subtract(int a, int b) => Console.WriteLine(a - b);

// Usage
MathOperation operation;
if (userChoice == "Add")
operation = Add;
else
operation = Subtract;

operation(10, 5); // Calls the chosen method

3. Multicast Delegates

Delegates can hold multiple methods and call them one after another. This is useful when you want to perform a series of actions in response to a single trigger.

public delegate void LogMessage(string message);

public void LogToFile(string message) => Console.WriteLine("File: " + message);
public void LogToConsole(string message) => Console.WriteLine("Console: " + message);

// Usage
LogMessage logger = LogToFile;
logger += LogToConsole; // Add another method
logger("Something happened!"); // Calls both methods

When to Use Events

Events are like notifications. They allow one part of your program to tell other parts, "Hey, something just happened!" Events are perfect for situations where you want to decouple the sender (publisher) from the receivers (subscribers). Here’s when you should use events:

1. Publisher-Subscriber Pattern

If you have a class that needs to notify other classes when something happens, use events. For example, a button in a UI can raise an event when clicked, and other parts of the program can respond to it.

public class Button
{
public event EventHandler Clicked;

public void Click()
{
	Console.WriteLine("Button clicked!");
	Clicked?.Invoke(this, EventArgs.Empty); // Notify subscribers
}
}

public class UserInterface
{
public void OnButtonClicked(object sender, EventArgs e)
{
	Console.WriteLine("UI updated!");
}
}

// Usage
var button = new Button();
var ui = new UserInterface();
button.Clicked += ui.OnButtonClicked; // Subscribe to the event
button.Click(); // Raises the event

2. Encapsulation

Events provide a secure way to notify others. Only the class that declares the event can raise it. This prevents outsiders from interfering with the event.

public class Alarm
{
public event EventHandler Ring;

public void TriggerAlarm()
{
	Console.WriteLine("Alarm is ringing!");
	Ring?.Invoke(this, EventArgs.Empty); // Only this class can raise the event
}
}

public class SecuritySystem
{
public void OnAlarmRang(object sender, EventArgs e)
{
	Console.WriteLine("Security system activated!");
}
}

// Usage
var alarm = new Alarm();
var security = new SecuritySystem();
alarm.Ring += security.OnAlarmRang; // Subscribe to the event
alarm.TriggerAlarm(); // Raises the event

3. Multiple Subscribers

Events allow multiple subscribers to respond to the same notification. For example, in a game, when a player scores, multiple systems (like UI, sound, and analytics) can react to the event.

public class Game
{
public event EventHandler PlayerScored;

public void Score()
{
	Console.WriteLine("Player scored a goal!");
	PlayerScored?.Invoke(this, EventArgs.Empty); // Notify all subscribers
}
}

public class SoundSystem
{
public void PlayCheerSound(object sender, EventArgs e)
{
	Console.WriteLine("Cheer sound played!");
}
}

public class UISystem
{
public void UpdateScoreDisplay(object sender, EventArgs e)
{
	Console.WriteLine("Score updated on UI!");
}
}

// Usage
var game = new Game();
var sound = new SoundSystem();
var ui = new UISystem();
game.PlayerScored += sound.PlayCheerSound; // Subscribe
game.PlayerScored += ui.UpdateScoreDisplay; // Subscribe
game.Score(); // Raises the event

Summary

  • Use Delegates when:
    • You need to pass methods as parameters.
    • You want to call methods dynamically at runtime.
    • You need multicast functionality (calling multiple methods in sequence).
  • Use Events when:
    • You want to notify multiple subscribers about something that happened.
    • You need encapsulation (only the declaring class can raise the event).
    • You’re implementing the observer pattern (publisher-subscriber model).

By understanding these scenarios, you can decide whether to use a delegate or an event in your code. For more detailed explanations, check out the dedicated pages on Delegates and Events.