📢 Delegates and Events: Your Code’s Messenger System
The Story of the Busy Restaurant
Imagine you’re running a busy restaurant. You have:
- A doorbell (delegate) that rings when customers arrive
- Waiters (event handlers) who respond to the bell
- A manager who makes sure everyone gets notified
This is exactly how delegates and events work in C#! They help different parts of your code talk to each other without being tightly connected.
🎯 What is a Delegate?
A delegate is like a sticky note with instructions. Instead of calling someone directly, you write down “call this person when X happens” and pass that note around.
Simple Example
Think of it like this:
- Your mom says: “When the pizza arrives, call me!”
- The sticky note says:
Call Mom() - When pizza comes, you look at the note and call mom
// Declare a delegate (the sticky note format)
delegate void NotifyPerson(string message);
// Create a method that matches the format
void CallMom(string message)
{
Console.WriteLine(quot;Mom: {message}");
}
// Use the delegate
NotifyPerson notify = CallMom;
notify("Pizza is here!");
// Output: Mom: Pizza is here!
Why Use Delegates?
| Without Delegates | With Delegates |
|---|---|
| Call methods directly | Pass methods like variables |
| Tight coupling | Loose coupling |
| Hard to change | Easy to swap behaviors |
🔗 Multicast Delegates: One Note, Many Calls
What if EVERYONE wants to know when pizza arrives? You can stack multiple methods onto one delegate!
The Family Pizza Alert
delegate void PizzaAlert(string message);
void NotifyMom(string msg)
{
Console.WriteLine(quot;Mom: {msg}");
}
void NotifyDad(string msg)
{
Console.WriteLine(quot;Dad: {msg}");
}
void NotifyKids(string msg)
{
Console.WriteLine(quot;Kids: YAY! {msg}");
}
// Stack them all!
PizzaAlert alert = NotifyMom;
alert += NotifyDad;
alert += NotifyKids;
alert("Pizza has arrived!");
Output:
Mom: Pizza has arrived!
Dad: Pizza has arrived!
Kids: YAY! Pizza has arrived!
Key Points About Multicast
- Use
+=to add methods - Use
-=to remove methods - They execute in the order you added them
// Remove dad (he's on a diet)
alert -= NotifyDad;
📦 Built-in Delegates: Ready-Made Sticky Notes
C# gives you three super useful delegates so you don’t have to create your own!
1. Action – Does Something, Returns Nothing
Like a worker who completes tasks but doesn’t report back.
// No parameters
Action sayHello = () => Console.WriteLine("Hello!");
sayHello();
// With parameters (up to 16!)
Action<string> greet = (name) =>
Console.WriteLine(quot;Hi, {name}!");
greet("Alex");
2. Func – Does Something, Returns a Result
Like a calculator that gives you answers.
// Returns int, takes two ints
Func<int, int, int> add = (a, b) => a + b;
int result = add(5, 3); // result = 8
// Just returns something
Func<string> getName = () => "Charlie";
3. Predicate – Asks Yes or No Questions
Like a security guard checking: “Can this person enter?”
Predicate<int> isAdult = (age) => age >= 18;
Console.WriteLine(isAdult(20)); // True
Console.WriteLine(isAdult(15)); // False
Quick Reference
| Delegate | Returns | Use When |
|---|---|---|
Action |
Nothing | You want to DO something |
Func |
A value | You need an ANSWER |
Predicate |
bool | You’re ASKING yes/no |
🎪 Events: The Safe Broadcast System
An event is like a radio station. Anyone can tune in (subscribe), but only the station owner can broadcast.
The Difference: Delegate vs Event
// DELEGATE: Anyone can invoke it (dangerous!)
public Action OnClick; // Outsiders can call this!
// EVENT: Only the owner can invoke (safe!)
public event Action OnClick; // Protected broadcast
Real Example: A Button Click
class Button
{
// Declare the event
public event Action OnClick;
// Only Button can trigger this
public void Click()
{
Console.WriteLine("Button clicked!");
OnClick?.Invoke();
}
}
// Using the button
Button myButton = new Button();
myButton.OnClick += () => Console.WriteLine("Do something!");
myButton.Click();
Output:
Button clicked!
Do something!
📬 Event Handlers and EventArgs
For serious events, C# has a standard pattern using EventHandler and EventArgs.
The Standard Pattern
Think of EventArgs as an envelope that carries extra information about what happened.
// Custom EventArgs (the envelope)
class OrderEventArgs : EventArgs
{
public string ItemName { get; set; }
public int Quantity { get; set; }
}
class Store
{
// Standard event pattern
public event EventHandler<OrderEventArgs> OrderPlaced;
public void PlaceOrder(string item, int qty)
{
Console.WriteLine("Order processing...");
// Trigger the event with data
OrderPlaced?.Invoke(this, new OrderEventArgs
{
ItemName = item,
Quantity = qty
});
}
}
Subscribing to the Event
Store shop = new Store();
// Subscribe with a handler method
shop.OrderPlaced += (sender, e) =>
{
Console.WriteLine(quot;Email sent: You ordered " +
quot;{e.Quantity} {e.ItemName}(s)");
};
shop.PlaceOrder("Pizza", 2);
Output:
Order processing...
Email sent: You ordered 2 Pizza(s)
Why Use This Pattern?
- sender tells you WHO triggered the event
- EventArgs tells you WHAT happened
- It’s the standard everyone recognizes
⚠️ Event Memory Leaks: The Forgotten Subscription
Here’s a scary story: forgetting to unsubscribe causes memory leaks!
The Problem
class NewsPublisher
{
public event EventHandler NewsPublished;
}
class NewsReader
{
public NewsReader(NewsPublisher pub)
{
// Subscribe to news
pub.NewsPublished += OnNewsReceived;
}
void OnNewsReceived(object sender, EventArgs e)
{
Console.WriteLine("Reading news...");
}
// ❌ Never unsubscribed! Memory leak!
}
Even when NewsReader should be garbage collected, the publisher still holds a reference to it through the event subscription!
The Solution: Always Unsubscribe!
class NewsReader : IDisposable
{
private NewsPublisher _publisher;
public NewsReader(NewsPublisher pub)
{
_publisher = pub;
_publisher.NewsPublished += OnNewsReceived;
}
void OnNewsReceived(object sender, EventArgs e)
{
Console.WriteLine("Reading news...");
}
// ✅ Clean up when done!
public void Dispose()
{
_publisher.NewsPublished -= OnNewsReceived;
}
}
// Usage
using (var reader = new NewsReader(publisher))
{
// ... use the reader
} // Auto-unsubscribes here!
Memory Leak Prevention Checklist
| Problem | Solution |
|---|---|
| Forgot to unsubscribe | Implement IDisposable |
| Long-lived publisher | Use weak references |
| Lambda subscribers | Store reference to unsubscribe |
// ❌ BAD: Can't unsubscribe lambda
button.Click += (s, e) => DoSomething();
// ✅ GOOD: Store the handler
EventHandler handler = (s, e) => DoSomething();
button.Click += handler;
// Later...
button.Click -= handler;
🎨 Visual Summary
graph TD A["Delegate"] --> B["Holds method reference"] B --> C["Can be invoked"] D["Multicast"] --> E["Multiple methods"] E --> F["Called in order"] G["Event"] --> H["Protected delegate"] H --> I["Only owner invokes"] J["EventArgs"] --> K["Carries data"] K --> L["Standard pattern"] M["Memory Leak"] --> N["Forgot unsubscribe"] N --> O["Use IDisposable"]
🏆 Key Takeaways
- Delegates = Methods as variables (sticky notes)
- Multicast = Stack multiple methods on one delegate
- Built-in delegates = Action, Func, Predicate
- Events = Safe, protected broadcasts
- EventHandler/EventArgs = Standard event pattern
- Memory leaks = Always unsubscribe from events!
🎯 Real-World Uses
| Scenario | What to Use |
|---|---|
| Button click handlers | event EventHandler Click |
| Callbacks after async | Action<Result> onComplete |
| Filtering collections | Predicate<T> |
| Transforming data | Func<T, TResult> |
| Logging/notifications | event EventHandler<LogArgs> |
Now you understand how C# components communicate! Events and delegates are the backbone of modern C# applications, from desktop apps to games to web services. 🚀
