Delegates and Events in C#: Callbacks & Publisher-Subscriber Pattern
1. Delegates and Events Overview
Q: What is a delegate in C#?
A delegate is a type-safe function pointer that defines a method signature, allowing methods to be passed as parameters or assigned to variables. Delegates enable flexible, decoupled code, often used for callbacks and event handling in C#. They are defined using the delegate keyword and typically reside in the System namespace (e.g., Action, Func).
Q: What is an event in C#?
An event is a mechanism that allows a class to notify other classes or objects when something occurs. Events are built on delegates, providing a publisher-subscriber model where a class (publisher) raises an event, and other classes (subscribers) handle it using event handlers.
Q: Why are delegates and events important?
- Delegates: Enable loose coupling by allowing methods to be passed dynamically, supporting callbacks, asynchronous programming, and event handling.
- Events: Provide a structured way to implement the observer pattern, common in GUI applications (e.g., button clicks) and asynchronous systems.
Q: How do delegates and events differ from C/C++?
- C# Delegates: Type-safe, managed, object-oriented, support multicast (multiple methods). Built into .NET.
- C# Events: Encapsulate delegates to ensure controlled subscription and invocation.
- C/C++: Use raw function pointers, not type-safe, no built-in event system. C++ has
std::functionbut lacks C#’s event model. - C# Advantage: Safer, more structured, with built-in support for events and multicast delegates.
2. Delegates Syntax and Usage
Q: What is the syntax for declaring a delegate in C#?
A delegate is declared using the delegate keyword, specifying a return type and parameter list.
Syntax:
delegate returnType DelegateName(parameterList);
Q: How do you use delegates in C#?
Delegates can be:
- Declared: Define a delegate type.
- Instantiated: Assign a method (matching the delegate’s signature) to a delegate variable.
- Invoked: Call the delegate to execute the assigned method(s).
- Multicast: Combine multiple methods using
+=to invoke them sequentially.
C# also provides built-in delegates:
Action<T>: For methods with no return value (void).Func<T, TResult>: For methods with a return value.
Q: What are common use cases for delegates?
- Callbacks (e.g., passing a method to process data).
- Event handling (e.g., responding to user actions).
- Asynchronous programming (e.g., with
async/await). - Functional programming (e.g., passing predicates to LINQ).
Q: Can you give an example of delegates in C#?
using System;
namespace DelegatesExample
{
// Declare a delegate
delegate void MessageDelegate(string message);
class Program
{
// Methods matching delegate signature
static void PrintMessage(string message)
{
Console.WriteLine($"Print: {message}");
}
static void LogMessage(string message)
{
Console.WriteLine($"Log: {message}");
}
static void Main(string[] args)
{
// Instantiate delegate
MessageDelegate del = PrintMessage;
// Invoke delegate
del("Hello, Delegate!");
// Multicast delegate
del += LogMessage;
del("Multicast test");
// Using built-in Action delegate
Action<string> action = m => Console.WriteLine($"Action: {m}");
action("Using Action delegate");
// Using Func delegate
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine($"Func result: {add(5, 3)}");
}
}
}
Output:
Print: Hello, Delegate!
Print: Multicast test
Log: Multicast test
Action: Using Action delegate
Func result: 8
3. Events and Event Handlers
Q: What is the syntax for declaring an event in C#?
An event is declared using the event keyword with a delegate type. Event handlers (methods) are subscribed using += and unsubscribed using -=.
Syntax:
event DelegateType EventName;
Q: How do events and event handlers work in C#?
- Publisher: A class that defines and raises an event using a delegate.
- Subscriber: A class or method that handles the event by subscribing to it.
- Event Handler: A method matching the delegate’s signature, invoked when the event is raised.
- Raising an Event: Use
EventName?.Invoke(args)to safely call all subscribed handlers.
C# provides a standard event pattern with EventHandler or EventHandler<TEventArgs>.
Q: What is the standard event pattern in C#?
The standard pattern uses EventHandler (for no custom data) or EventHandler<TEventArgs> (for custom data), where:
EventHandler:Signature isvoid (object sender, EventArgs e).TEventArgs:A custom class derived fromEventArgsto pass event data.
Q: Can you give an example of events and event handlers in C#?
using System;
namespace EventsExample
{
// Custom EventArgs for event data
class TemperatureChangedEventArgs : EventArgs
{
public double NewTemperature { get; }
public TemperatureChangedEventArgs(double temp)
{
NewTemperature = temp;
}
}
// Publisher class
class Thermostat
{
// Declare event using EventHandler<T>
public event EventHandler<TemperatureChangedEventArgs> TemperatureChanged;
private double temperature;
public double Temperature
{
get => temperature;
set
{
temperature = value;
// Raise event
TemperatureChanged?.Invoke(this, new TemperatureChangedEventArgs(temperature));
}
}
}
// Subscriber class
class Display
{
public void Subscribe(Thermostat thermostat)
{
thermostat.TemperatureChanged += Thermostat_TemperatureChanged;
}
private void Thermostat_TemperatureChanged(object sender, TemperatureChangedEventArgs e)
{
Console.WriteLine($"Temperature changed to: {e.NewTemperature:F1}°C");
}
}
class Program
{
static void Main(string[] args)
{
Thermostat thermostat = new Thermostat();
Display display = new Display();
// Subscribe to event
display.Subscribe(thermostat);
// Trigger event by changing temperature
thermostat.Temperature = 22.5;
thermostat.Temperature = 25.0;
// Unsubscribe (optional)
thermostat.TemperatureChanged -= display.Thermostat_TemperatureChanged;
thermostat.Temperature = 30.0; // No output, as unsubscribed
}
}
}
Output:
Temperature changed to: 22.5°C
Temperature changed to: 25.0°C
Q: How do events differ from C/C++?
- C#: Events are a first-class feature, built on delegates, ensuring controlled subscription and invocation. Standard pattern with
EventHandler. - C/C++: No built-in event system; events are emulated with function pointers or callbacks, less structured.
- C# Advantage: Type-safe, encapsulated, with built-in support for publisher-subscriber patterns.
4. Common Mistakes & Best Practices
Q: Common mistakes?
Delegates:
- Declaring delegate signatures that don’t match target methods.
- Forgetting to check for null before invoking a delegate (fixed with
?.Invoke). - Overusing custom delegates instead of
ActionorFunc.
Events:
- Invoking events directly (e.g.,
EventName()instead ofEventName?.Invoke()). - Not unsubscribing from events, causing memory leaks.
- Defining non-standard event signatures, ignoring
EventHandler. - Forgetting to derive custom
EventArgsfromSystem.EventArgs.
Q: Best practices?
Delegates:
- Use built-in
ActionorFuncfor common scenarios instead of custom delegates. - Ensure delegate signatures match method signatures exactly.
- Use
?.Invoketo safely invoke delegates, preventing null reference exceptions. - Keep delegate usage simple to avoid complex callback chains.
Events:
- Follow the standard event pattern with
EventHandlerorEventHandler<TEventArgs>. - Use
TEventArgsderived fromEventArgsfor custom event data. - Always check for null with
?.Invokewhen raising events. - Unsubscribe (
-=) from events in subscribers to prevent memory leaks.
General:
- Encapsulate events to prevent external invocation (e.g., use
add/removeaccessors). - Use modern C# features (e.g., lambda expressions for inline delegates).
- Document delegates and events with XML comments to clarify their purpose.
- Test event handlers for correct behavior and edge cases.