Back to Curriculum

Delegates and Events

📚 Lesson 11 of 15 ⏱️ 45 min

Delegates and Events

45 min

Delegates are type-safe function pointers that can reference methods, enabling you to pass methods as parameters and create callback mechanisms. Delegates define method signatures (parameters and return type). Multiple methods can be attached to a delegate (multicast). Understanding delegates enables flexible, decoupled code. Delegates are fundamental to C# event handling.

Events are a way to provide notifications when something happens, enabling loose coupling between publishers and subscribers. Events are built on delegates but provide encapsulation (only the declaring class can raise them). Events enable the observer pattern. Understanding events enables reactive programming. Events are essential for UI and component communication.

Lambda expressions provide concise syntax for delegate creation, enabling inline method definitions without separate method declarations. Lambda syntax: (parameters) => expression or (parameters) => { statements }. Lambdas can capture variables from enclosing scope (closures). Understanding lambdas enables concise, expressive code. Lambdas are widely used in LINQ and event handlers.

Action and Func are built-in delegate types, eliminating the need to define custom delegates for common scenarios. Action represents methods that return void. Func<T> represents methods that return T. Action and Func support up to 16 parameters. Understanding Action/Func enables using delegates without custom definitions. Action/Func are convenient for common scenarios.

Delegates and events enable the observer pattern, where objects notify subscribers of state changes. This pattern is common in UI frameworks (button clicks, property changes) and component architectures. Understanding the observer pattern enables decoupled, reactive designs. Events are the primary mechanism for the observer pattern in C#.

Best practices include using events for notifications, using Action/Func for simple delegates, using lambda expressions for concise code, properly unsubscribing from events to prevent memory leaks, and using null-conditional operators (?.) when raising events. Understanding delegates and events enables flexible, decoupled architectures. Delegates and events are essential for modern C#.

Key Concepts

  • Delegates are type-safe function pointers that reference methods.
  • Events provide notifications with encapsulation.
  • Lambda expressions provide concise delegate syntax.
  • Action and Func are built-in delegate types.
  • Delegates and events enable the observer pattern.

Learning Objectives

Master

  • Creating and using delegates
  • Implementing events for notifications
  • Using lambda expressions for delegates
  • Working with Action and Func types

Develop

  • Understanding the observer pattern
  • Designing decoupled, reactive architectures
  • Appreciating delegates' role in C#

Tips

  • Use events for notifications—they provide encapsulation.
  • Use lambda expressions for concise delegate creation.
  • Use Action for void methods, Func<T> for methods returning T.
  • Unsubscribe from events to prevent memory leaks.

Common Pitfalls

  • Not unsubscribing from events, causing memory leaks.
  • Raising events without null checks, causing NullReferenceException.
  • Creating custom delegates when Action/Func would work.
  • Not understanding closures in lambdas, causing unexpected behavior.

Summary

  • Delegates are type-safe function pointers.
  • Events provide notifications with encapsulation.
  • Lambda expressions enable concise delegate creation.
  • Action and Func are built-in delegate types.
  • Understanding delegates and events enables flexible architectures.

Exercise

Create delegates and events to demonstrate event-driven programming.

using System;

// Custom delegate
public delegate void MessageHandler(string message);

// Event publisher
public class Publisher
{
    // Custom event
    public event MessageHandler MessageReceived;
    
    // Built-in event with EventHandler
    public event EventHandler<string> DataProcessed;
    
    public void ProcessData(string data)
    {
        Console.WriteLine($"Processing: {data}");
        
        // Raise custom event
        MessageReceived?.Invoke($"Custom event: {data}");
        
        // Raise built-in event
        DataProcessed?.Invoke(this, $"Processed: {data}");
    }
}

// Event subscriber
public class Subscriber
{
    public void OnMessageReceived(string message)
    {
        Console.WriteLine($"Subscriber received: {message}");
    }
    
    public void OnDataProcessed(object sender, string data)
    {
        Console.WriteLine($"Subscriber processed: {data}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        var publisher = new Publisher();
        var subscriber = new Subscriber();
        
        // Subscribe to events
        publisher.MessageReceived += subscriber.OnMessageReceived;
        publisher.DataProcessed += subscriber.OnDataProcessed;
        
        // Using Action delegate
        Action<string> printAction = message => Console.WriteLine($"Action: {message}");
        printAction("Hello from Action!");
        
        // Using Func delegate
        Func<int, int, int> addFunc = (a, b) => a + b;
        Console.WriteLine($"Func result: {addFunc(5, 3)}");
        
        // Trigger events
        publisher.ProcessData("Sample data");
    }
}

Code Editor

Output