DEV Community

souvikk27
souvikk27

Posted on

Event Handling In C#

In C#, events are a powerful feature that allows objects to communicate with each other. Events are based on the observer pattern, where an object (the subject) notifies other objects (observers) when something of interest occurs. Events in C# are typically implemented using delegates, which are types that represent references to methods with a specific signature.

Here’s an in-depth breakdown of all the possible ways to create and use events in C#, including examples with Action, Func, and custom delegates.

1. Using EventHandler Delegate

The most common way to declare an event in C# is by using the EventHandler delegate or EventHandler<TEventArgs>, which follows a specific signature.

Example:

public class EventPublisher
{
    // Event based on the built-in EventHandler delegate
    public event EventHandler EventOccured;

    public void TriggerEvent()
    {
        // Check if there are any subscribers
        EventOccured?.Invoke(this, EventArgs.Empty);
    }
}

public class EventSubscriber
{
    public void OnEventOccured(object sender, EventArgs e)
    {
        Console.WriteLine("Event has occurred!");
    }
}

class Program
{
    static void Main(string[] args)
    {
        EventPublisher publisher = new EventPublisher();
        EventSubscriber subscriber = new EventSubscriber();

        // Subscribe to the event
        publisher.EventOccured += subscriber.OnEventOccured;

        // Trigger the event
        publisher.TriggerEvent();
    }
}

Enter fullscreen mode Exit fullscreen mode

Here, EventHandler is a predefined delegate with a signature that takes two parameters: the sender (of type object) and an EventArgs (or derived type).

2. Using EventHandler with Custom EventArgs

You can define a custom class derived from EventArgs to pass more information when the event is triggered.

Example:

public class CustomEventArgs : EventArgs
{
    public string Message { get; }

    public CustomEventArgs(string message)
    {
        Message = message;
    }
}

public class EventPublisher
{
    public event EventHandler<CustomEventArgs> EventOccured;

    public void TriggerEvent(string message)
    {
        EventOccured?.Invoke(this, new CustomEventArgs(message));
    }
}

public class EventSubscriber
{
    public void OnEventOccured(object sender, CustomEventArgs e)
    {
        Console.WriteLine($"Event message: {e.Message}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        EventPublisher publisher = new EventPublisher();
        EventSubscriber subscriber = new EventSubscriber();

        publisher.EventOccured += subscriber.OnEventOccured;
        publisher.TriggerEvent("Hello from the event!");
    }
}

Enter fullscreen mode Exit fullscreen mode

Here, CustomEventArgs contains extra data (Message) passed to the event handlers.

3. Using Custom Delegate

Instead of using the predefined EventHandler, you can create your own delegate to define a custom event signature.

Example:

public delegate void CustomEventDelegate(string message);

public class EventPublisher
{
    public event CustomEventDelegate EventOccured;

    public void TriggerEvent(string message)
    {
        EventOccured?.Invoke(message);
    }
}

public class EventSubscriber
{
    public void OnEventOccured(string message)
    {
        Console.WriteLine($"Event message: {message}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        EventPublisher publisher = new EventPublisher();
        EventSubscriber subscriber = new EventSubscriber();

        publisher.EventOccured += subscriber.OnEventOccured;
        publisher.TriggerEvent("Hello from the custom delegate event!");
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Using Action Delegate

The Action delegate is part of the .NET framework and represents a method that has no return value. It can take parameters, which makes it suitable for events.

Example:

public class EventPublisher
{
    public event Action<string> EventOccured;

    public void TriggerEvent(string message)
    {
        EventOccured?.Invoke(message);
    }
}

public class EventSubscriber
{
    public void OnEventOccured(string message)
    {
        Console.WriteLine($"Action Event message: {message}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        EventPublisher publisher = new EventPublisher();
        EventSubscriber subscriber = new EventSubscriber();

        publisher.EventOccured += subscriber.OnEventOccured;
        publisher.TriggerEvent("Hello from Action-based event!");
    }
}

Enter fullscreen mode Exit fullscreen mode

Here, the Action<string> delegate is used, which takes one parameter and returns void.

5. Using Func Delegate

The Func delegate represents a method that returns a value. Though it's less common for events to return values (since events usually notify listeners, not ask for feedback), you can use Func to design an event that expects a return value.

Example:

public class EventPublisher
{
    public event Func<string, bool> EventOccured;

    public void TriggerEvent(string message)
    {
        bool result = EventOccured?.Invoke(message) ?? false;
        Console.WriteLine($"Event result: {result}");
    }
}

public class EventSubscriber
{
    public bool OnEventOccured(string message)
    {
        Console.WriteLine($"Func Event message: {message}");
        return message.Contains("success");
    }
}

class Program
    {
    static void Main(string[] args)
    {
        EventPublisher publisher = new EventPublisher();
        EventSubscriber subscriber = new EventSubscriber();

        publisher.EventOccured += subscriber.OnEventOccured;
        publisher.TriggerEvent("Hello with success!");
    }
}

Enter fullscreen mode Exit fullscreen mode

Here, Func<string, bool> defines an event that returns a bool based on the message passed.

6. Anonymous Methods for Event Handling

You can also subscribe to events using anonymous methods or lambda expressions.

Example:

public class EventPublisher
{
    public event EventHandler EventOccured;

    public void TriggerEvent()
    {
        EventOccured?.Invoke(this, EventArgs.Empty);
    }
}

class Program
{
    static void Main(string[] args)
    {
        EventPublisher publisher = new EventPublisher();

        // Subscribe with an anonymous method
        publisher.EventOccured += delegate (object sender, EventArgs e)
        {
            Console.WriteLine("Event handled using anonymous method");
        };

        // Subscribe with a lambda expression
        publisher.EventOccured += (sender, e) =>
        {
            Console.WriteLine("Event handled using lambda expression");
        };

        publisher.TriggerEvent();
    }
}
Enter fullscreen mode Exit fullscreen mode

In this case, the event handler is specified inline using anonymous methods or lambda expressions.

7. Explicit Event Handling Using Interface

You can define events explicitly as part of an interface, and classes that implement this interface can provide event handling.

Example:

public interface IEventPublisher
{
    event EventHandler EventOccured;
}

public class EventPublisher : IEventPublisher
{
    public event EventHandler EventOccured;

    public void TriggerEvent()
    {
        EventOccured?.Invoke(this, EventArgs.Empty);
    }
}

public class EventSubscriber
{
    public void OnEventOccured(object sender, EventArgs e)
    {
        Console.WriteLine("Event handled from interface");
    }
}

class Program
{
    static void Main(string[] args)
    {
        EventPublisher publisher = new EventPublisher();
        EventSubscriber subscriber = new EventSubscriber();

        IEventPublisher iPublisher = publisher;
        iPublisher.EventOccured += subscriber.OnEventOccured;

        publisher.TriggerEvent();
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the IEventPublisher interface defines the event, and classes like EventPublisher must implement it.

8. Multi-cast Delegates and Events

C# events can be multi-cast, meaning multiple methods can be invoked in response to a single event trigger.

Example:

public class EventPublisher
{
    public event Action<string> EventOccured;

    public void TriggerEvent(string message)
    {
        EventOccured?.Invoke(message);
    }
}

public class EventSubscriber
{
    public void FirstHandler(string message)
    {
        Console.WriteLine($"First handler: {message}");
    }

    public void SecondHandler(string message)
    {
        Console.WriteLine($"Second handler: {message}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        EventPublisher publisher = new EventPublisher();
        EventSubscriber subscriber = new EventSubscriber();

        publisher.EventOccured += subscriber.FirstHandler;
        publisher.EventOccured += subscriber.SecondHandler;

        publisher.TriggerEvent("Hello to multiple handlers!");
    }
}

Enter fullscreen mode Exit fullscreen mode

Here, both FirstHandler and SecondHandler are invoked when the event is triggered.


These are the most common ways to create and handle events in C#. Whether using built-in delegates like Action, Func, or EventHandler, or creating custom ones, events allow you to build robust, decoupled systems.

Let summarize with a final implementation

using System;

public class EventExamples
{
    // 1. Traditional event declaration with custom EventArgs
    public class CustomEventArgs : EventArgs
    {
        public string Message { get; set; }
    }
    public event EventHandler<CustomEventArgs> TraditionalEvent;

    // 2. Event using Action delegate
    public event Action SimpleEvent;

    // 3. Event using Action<T> delegate
    public event Action<string> ParameterizedEvent;

    // 4. Event using Func<T, TResult> delegate
    public event Func<string, bool> FuncEvent;

    // 5. Custom delegate event
    public delegate void CustomDelegate(string message, int number);
    public event CustomDelegate CustomEvent;

    // 6. Event with multiple parameters using Action
    public event Action<string, int, bool> MultiParamEvent;

    // Methods to raise the events
    public void RaiseTraditionalEvent(string message)
    {
        TraditionalEvent?.Invoke(this, new CustomEventArgs { Message = message });
    }

    public void RaiseSimpleEvent()
    {
        SimpleEvent?.Invoke();
    }

    public void RaiseParameterizedEvent(string message)
    {
        ParameterizedEvent?.Invoke(message);
    }

    public bool RaiseFuncEvent(string input)
    {
        return FuncEvent?.Invoke(input) ?? false;
    }

    public void RaiseCustomEvent(string message, int number)
    {
        CustomEvent?.Invoke(message, number);
    }

    public void RaiseMultiParamEvent(string message, int number, bool flag)
    {
        MultiParamEvent?.Invoke(message, number, flag);
    }
}

// Usage example
class Program
{
    static void Main()
    {
        var examples = new EventExamples();

        // Subscribe to events
        examples.TraditionalEvent += (sender, e) => Console.WriteLine($"Traditional event: {e.Message}");
        examples.SimpleEvent += () => Console.WriteLine("Simple event fired");
        examples.ParameterizedEvent += message => Console.WriteLine($"Parameterized event: {message}");
        examples.FuncEvent += input => { Console.WriteLine($"Func event: {input}"); return true; };
        examples.CustomEvent += (message, number) => Console.WriteLine($"Custom event: {message}, {number}");
        examples.MultiParamEvent += (message, number, flag) => Console.WriteLine($"Multi-param event: {message}, {number}, {flag}");

        // Raise events
        examples.RaiseTraditionalEvent("Hello, World!");
        examples.RaiseSimpleEvent();
        examples.RaiseParameterizedEvent("Event with parameter");
        bool result = examples.RaiseFuncEvent("Func event input");
        examples.RaiseCustomEvent("Custom message", 42);
        examples.RaiseMultiParamEvent("Multi-param", 100, true);
    }
}
Enter fullscreen mode Exit fullscreen mode

Here's a brief explanation of each method:

  1. Traditional event: Uses EventHandler<T> with custom EventArgs.
  2. Simple event: Uses Action delegate for parameterless events.
  3. Parameterized event: Uses Action<T> for events with one parameter.
  4. Func event: Uses Func<T, TResult> for events that return a value.
  5. Custom delegate event: Defines a custom delegate for more complex event signatures.
  6. Multi-parameter event: Uses Action<T1, T2, T3> for events with multiple parameters.

Top comments (2)

Collapse
 
victorbustos2002 profile image
Victor Bustos

Excellent article, well structured.
thanks...

Collapse
 
karenpayneoregon profile image
Karen Payne

Well written. Consider adding code samples that the readers can try out which could be done via a GitHub repository.