What are delegates, events, and how do they differ?
10 minintermediate.NETdelegatesevents
Quick Answer
Delegates are type-safe function pointers that can be called directly and support multicast. Events are special delegates with restrictions - they can only be invoked from within the declaring class and only support += and -= operations. Events provide better encapsulation and implement the publisher-subscriber pattern safely.
Detailed Answer
Delegates:
- Type-safe function pointers
- Reference type that holds references to methods
- Can point to any method with matching signature
- Supports multicast (calling multiple methods)
- Can be called directly by any code that has access
Events:
- Built on top of delegates
- Special kind of delegate with restrictions
- Provides publisher-subscriber pattern
- Can only be invoked from within the declaring class
- Add/remove methods only (cannot be assigned from outside)
Delegate Example:
// Define delegate type
public delegate void NotificationHandler(string message);
public class Example
{
// Delegate field - can be called and assigned from anywhere
public NotificationHandler OnNotification;
public void SendNotification(string msg)
{
// Anyone can call this delegate
OnNotification?.Invoke(msg);
}
}
// Usage
Example example = new Example();
// Problem: Can be assigned from outside, replacing all subscribers!
example.OnNotification = (msg) => Console.WriteLine($"Handler 1: {msg}");
// Problem: Can be called from outside the class!
example.OnNotification?.Invoke("Direct call");
Event Example:
// Define delegate type
public delegate void NotificationHandler(string message);
public class Example
{
// Event - restricted delegate
public event NotificationHandler OnNotification;
public void SendNotification(string msg)
{
// Only the class itself can invoke the event
OnNotification?.Invoke(msg);
}
}
// Usage
Example example = new Example();
// Can only add/remove handlers (+=, -=)
example.OnNotification += (msg) => Console.WriteLine($"Handler 1: {msg}");
example.OnNotification += (msg) => Console.WriteLine($"Handler 2: {msg}");
// ERROR: Cannot assign directly
// example.OnNotification = (msg) => Console.WriteLine("Test");
// ERROR: Cannot call from outside
// example.OnNotification?.Invoke("Test");
Built-in EventHandler:
public class Button
{
// Using built-in EventHandler delegate
public event EventHandler Click;
// Using generic EventHandler with custom args
public event EventHandler<MouseEventArgs> MouseMove;
protected virtual void OnClick()
{
Click?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnMouseMove(MouseEventArgs e)
{
MouseMove?.Invoke(this, e);
}
}
// Custom EventArgs
public class MouseEventArgs : EventArgs
{
public int X { get; set; }
public int Y { get; set; }
}
// Usage
Button button = new Button();
button.Click += (sender, e) => Console.WriteLine("Button clicked!");
button.MouseMove += (sender, e) => Console.WriteLine($"Mouse at {e.X}, {e.Y}");
Multicast Delegates:
public delegate void LogHandler(string message);
public class Logger
{
private LogHandler logHandler;
public void AddLogMethod(LogHandler handler)
{
logHandler += handler; // Add to invocation list
}
public void RemoveLogMethod(LogHandler handler)
{
logHandler -= handler; // Remove from invocation list
}
public void Log(string message)
{
// Calls all methods in invocation list
logHandler?.Invoke(message);
}
}
// Usage
Logger logger = new Logger();
logger.AddLogMethod(msg => Console.WriteLine($"Console: {msg}"));
logger.AddLogMethod(msg => File.AppendAllText("log.txt", msg));
logger.AddLogMethod(msg => Debug.WriteLine(msg));
logger.Log("Error occurred"); // Calls all three methods
Key Differences:
| Feature | Delegate | Event |
|---|---|---|
| Assignment | Can be directly assigned (=) | Cannot be assigned, only += or -= |
| Invocation | Can be called from anywhere | Can only be invoked within declaring class |
| Purpose | General callback mechanism | Publisher-subscriber pattern |
| Encapsulation | Weak | Strong |
| Protection | No protection from outside | Protected from outside manipulation |
Real-world Example - Stock Market:
public class Stock
{
private decimal price;
// Event with custom EventArgs
public event EventHandler<PriceChangedEventArgs> PriceChanged;
public decimal Price
{
get => price;
set
{
if (price != value)
{
decimal oldPrice = price;
price = value;
OnPriceChanged(new PriceChangedEventArgs(oldPrice, value));
}
}
}
protected virtual void OnPriceChanged(PriceChangedEventArgs e)
{
PriceChanged?.Invoke(this, e);
}
}
public class PriceChangedEventArgs : EventArgs
{
public decimal OldPrice { get; }
public decimal NewPrice { get; }
public decimal Change => NewPrice - OldPrice;
public PriceChangedEventArgs(decimal oldPrice, decimal newPrice)
{
OldPrice = oldPrice;
NewPrice = newPrice;
}
}
// Subscribers
public class StockAlert
{
public StockAlert(Stock stock)
{
stock.PriceChanged += OnPriceChanged;
}
private void OnPriceChanged(object sender, PriceChangedEventArgs e)
{
if (e.Change > 10)
Console.WriteLine($"Alert: Price jumped by {e.Change:C}");
}
}
public class StockLogger
{
public StockLogger(Stock stock)
{
stock.PriceChanged += OnPriceChanged;
}
private void OnPriceChanged(object sender, PriceChangedEventArgs e)
{
Console.WriteLine($"Price changed from {e.OldPrice:C} to {e.NewPrice:C}");
}
}
// Usage
Stock appleStock = new Stock { Price = 150 };
StockAlert alert = new StockAlert(appleStock);
StockLogger logger = new StockLogger(appleStock);
appleStock.Price = 165; // Both alert and logger are notified
Action and Func Delegates:
// Action - no return value
Action<string> logAction = msg => Console.WriteLine(msg);
Action<int, int> addAction = (x, y) => Console.WriteLine(x + y);
// Func - with return value (last type parameter is return type)
Func<int, int, int> add = (x, y) => x + y;
Func<string, bool> isValid = str => !string.IsNullOrEmpty(str);
// Usage
int result = add(5, 3); // 8
bool valid = isValid("test"); // true
When to use what:
- Use delegates when you need direct method reference or callbacks
- Use events when implementing observer/publisher-subscriber pattern
- Use Action/Func for simple inline delegates (LINQ, callbacks)
- Use custom delegates when you need specific naming or complex signatures