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();
}
}
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!");
}
}
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!");
}
}
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!");
}
}
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!");
}
}
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();
}
}
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();
}
}
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!");
}
}
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);
}
}
Here's a brief explanation of each method:
- Traditional event: Uses
EventHandler<T>
with customEventArgs
. - Simple event: Uses
Action
delegate for parameterless events. - Parameterized event: Uses
Action<T>
for events with one parameter. - Func event: Uses
Func<T, TResult>
for events that return a value. - Custom delegate event: Defines a custom delegate for more complex event signatures.
- Multi-parameter event: Uses
Action<T1, T2, T3>
for events with multiple parameters.
Top comments (2)
Excellent article, well structured.
thanks...
Well written. Consider adding code samples that the readers can try out which could be done via a GitHub repository.