C# > Advanced C# > Delegates and Events > Event Declaration and Handling

Custom Event Declaration and Handling in C#

This code snippet demonstrates how to declare a custom event in C# using delegates and how to handle that event in another class. Events allow classes to notify other classes when something interesting happens. This is a fundamental aspect of building loosely coupled and responsive applications.

Basic Event Declaration

This example demonstrates a typical event pattern in C#. First, a delegate `DataProcessedEventHandler` is defined, representing the signature of the event handler. Then, an `EventArgs` derived class `DataProcessedEventArgs` holds the data associated with the event. The `DataProcessor` class declares the `DataProcessed` event and defines a `ProcessData` method that, after simulating data processing, raises the event using `OnDataProcessed`. The `DataConsumer` class subscribes to the event using the `+=` operator and handles the event in the `Processor_DataProcessed` method. Unsubscribing from the event is done via the `-=` operator.

using System;

// 1. Define a delegate type for the event
public delegate void DataProcessedEventHandler(object sender, DataProcessedEventArgs e);

// 2. Define a class to hold event data (optional but recommended)
public class DataProcessedEventArgs : EventArgs
{
    public string Data { get; set; }
    public DateTime ProcessedTime { get; set; }

    public DataProcessedEventArgs(string data)
    {
        Data = data;
        ProcessedTime = DateTime.Now;
    }
}

// 3. Define a class that will raise the event
public class DataProcessor
{
    // 4. Declare the event using the delegate type
    public event DataProcessedEventHandler DataProcessed;

    public void ProcessData(string data)
    {
        Console.WriteLine($"Processing data: {data}");
        // Simulate some processing
        System.Threading.Thread.Sleep(1000);  // Simulate work

        // 5. Raise the event (if there are subscribers)
        OnDataProcessed(new DataProcessedEventArgs(data));
    }

    // 6. Create a protected virtual method to raise the event
    protected virtual void OnDataProcessed(DataProcessedEventArgs e)
    {
        // Make a copy to be thread safe.
        DataProcessedEventHandler handler = DataProcessed;

        // Only raise the event if there are subscribers.
        if (handler != null)
        {
            handler(this, e);
        }
    }
}

// 7. Define a class that will subscribe to the event
public class DataConsumer
{
    public void Subscribe(DataProcessor processor)
    {
        // 8. Subscribe to the event
        processor.DataProcessed += Processor_DataProcessed;
    }

    private void Processor_DataProcessed(object sender, DataProcessedEventArgs e)
    {
        Console.WriteLine($"Data processed: {e.Data} at {e.ProcessedTime}");
    }

    public void Unsubscribe(DataProcessor processor)
    {
         processor.DataProcessed -= Processor_DataProcessed;
    }
}

public class Example
{
    public static void Main(string[] args)
    {
        // Usage
        DataProcessor processor = new DataProcessor();
        DataConsumer consumer = new DataConsumer();

        // Subscribe the consumer to the processor's event
        consumer.Subscribe(processor);

        // Process some data (this will raise the event)
        processor.ProcessData("Important Data");

        consumer.Unsubscribe(processor);

        processor.ProcessData("Data after unsubscribe");

        Console.ReadKey();
    }
}

Concepts Behind the Snippet

This snippet highlights several key concepts: * Delegates: Type-safe function pointers that define the signature of the event handler. * Events: A mechanism that allows a class or object to notify other classes or objects when something of interest occurs. Events use delegates under the hood. * Event Handlers: Methods that are executed when an event is raised. * EventArgs: A base class for event data that can be extended to include custom data specific to the event. * Publisher-Subscriber Pattern: Events implement the publisher-subscriber pattern, where the class that raises the event (the publisher) doesn't need to know the details of the classes that handle the event (the subscribers).

Real-Life Use Case

Consider a UI application where a button click needs to trigger an action in another part of the application. The button class can define a `Click` event. Other parts of the application can subscribe to this event and execute their specific logic when the button is clicked. This promotes loose coupling because the button doesn't need to know what specific actions are performed when it's clicked.

Best Practices

  • Use `EventArgs` derived classes: Always create a class derived from `EventArgs` to hold event-related data, even if the event doesn't need to pass any data initially. This makes it easier to add data to the event later without breaking existing subscribers.
  • Use a protected virtual `OnEventName` method: Create a protected virtual method to raise the event. This allows derived classes to override the event raising behavior.
  • Check for null subscribers: Always check if there are any subscribers to the event before raising it to avoid `NullReferenceException` errors. This can be achieved using `DataProcessedEventHandler handler = DataProcessed; if (handler != null) { handler(this, e); }`. This is also thread-safe, preventing a race condition where a subscriber unsubscribes between the null check and the event invocation.
  • Unsubscribe when no longer needed: Always unsubscribe from events when you no longer need to receive notifications to prevent memory leaks.

Interview Tip

Be prepared to explain the difference between delegates and events. Delegates are type-safe function pointers, while events are a language construct built on top of delegates that provide a controlled way for classes to subscribe to and be notified of events. Events encapsulate delegates to prevent subscribers from directly invoking the delegate, only allowing them to add or remove handlers.

When to Use Them

Use events when you need to implement the publisher-subscriber pattern, where one class (the publisher) needs to notify other classes (the subscribers) when something interesting happens, without knowing the specific details of the subscribers. This is especially useful for UI applications, asynchronous operations, and decoupled systems.

Alternatives

  • Interfaces: Interfaces can be used to achieve similar results, but they require the implementing class to know the specific methods to call, which can lead to tighter coupling.
  • Reactive Extensions (Rx): Rx provides a more powerful and flexible way to handle asynchronous data streams and events, but it adds complexity.
  • Direct Method Calls: Can lead to tight coupling and reduced flexibility.

Pros

  • Loose Coupling: The publisher doesn't need to know the details of the subscribers.
  • Flexibility: Subscribers can easily subscribe to and unsubscribe from events.
  • Extensibility: New subscribers can be added without modifying the publisher.

Cons

  • Potential Memory Leaks: If subscribers are not properly unsubscribed, they can prevent objects from being garbage collected.
  • Complexity: Understanding and implementing events can be more complex than simple method calls.
  • Debugging: Debugging event-driven code can be more challenging than debugging synchronous code.

FAQ

  • What is the difference between a delegate and an event?

    A delegate is a type-safe function pointer that defines the signature of a method. An event is a language construct built on top of delegates that provides a controlled way for classes to subscribe to and be notified of events. Events encapsulate delegates to prevent subscribers from directly invoking the delegate, only allowing them to add or remove handlers.
  • Why should I use a protected virtual `OnEventName` method to raise an event?

    Using a protected virtual `OnEventName` method allows derived classes to override the event raising behavior. This provides greater flexibility and extensibility.
  • How do I prevent memory leaks when using events?

    Always unsubscribe from events when you no longer need to receive notifications. This prevents objects from being garbage collected because they are still referenced by the event's delegate list.