CodeGym /Courses /C# SELF /Standard event pattern ( Ev...

Standard event pattern ( EventHandler/ EventArgs)

C# SELF
Level 52 , Lesson 3
Available

1. Introduction

Let's recall: an event is a public contract, a promise from your code to "call" subscribers when something important happens. In examples from previous lectures you might have seen declarations like:


public event Action<string> MessageSent;

Or even:


public delegate void MyHandler(int value);
public event MyHandler SomethingHappened;

That works, but this approach creates a bunch of problems — from inconsistent method signatures to not knowing who raised the event and what exactly happened. Imagine if a keyboard's key fired randomly on each press, behaving differently every time in the same program for the same task. Say you're playing a game and press space. A moment ago it meant jump, now it unexpectedly opens the inventory. Nightmare, not a standard!

.NET has a sort of Constitution for events — it's a convention about the handler signature and the structure of information passed with an event. Here's the canonical style:


void Handler(object sender, EventArgs args);

Sound familiar? You see it everywhere: from WinForms button clicks to system events in ASP.NET and even in third-party libraries.

2. What are EventHandler and EventArgs

The main idea: every event reports two things:

  • Who raised the event? sender
  • What happened? EventArgs — additional data

In C# this is expressed like this:


public delegate void EventHandler(object sender, EventArgs e);
  • object sender — a reference to the event initiator. It can be anything — just this.
  • EventArgs e — an object with extra information about the event. For simple scenarios use EventArgs.Empty, for complex ones create your own subclasses of EventArgs.

Fun fact

In official .NET guidelines for public APIs it's customary for an event to have the signature (object sender, EventArgs e). If you see an event without sender and EventArgs — it's likely an author's simplified variant, not the canonical .NET style.

The big benefit of one standard

With a single standard, events are easier to log, test, subscribe to generically and extend. Open the sources of WinForms, WPF, ASP.NET — you'll see the same pattern.

3. How to use the standard event pattern

1. Use the built-in delegate EventHandler

Instead of declaring your own delegate you can use the built-in one:


public event EventHandler SmthHappened;

Now the handler always looks the same:


private void OnSmthHappened(object sender, EventArgs e)
{
    // Logic to react to the event
}

Subscribing is still simple:


myObj.SmthHappened += OnSmthHappened;

Important: if the event doesn't pass extra data, use EventArgs.Empty.

2. Creating custom event arguments

If you need to pass information (a computation result, a filename, an error), create a subclass of EventArgs:


public class CalculationEventArgs : EventArgs
{
    public double Result { get; }
    public CalculationEventArgs(double result) => Result = result;
}

Then use the generic delegate — EventHandler<TEventArgs>:


public event EventHandler<CalculationEventArgs> CalculationFinished;

The handler now receives your specific argument type:


private void OnCalculationFinished(object sender, CalculationEventArgs e)
{
    Console.WriteLine($"Calculation finished. Result: {e.Result}");
}

4. Sample application

Let's evolve our tutorial project — suppose we have a calculator that adds or subtracts numbers and notifies about operation completion via an event.

Minimal example


public class Calculator
{
    public event EventHandler<CalculationEventArgs> CalculationPerformed;

    public void Add(int a, int b)
    {
        int result = a + b;
        // "Fire" the event
        CalculationPerformed?.Invoke(this, new CalculationEventArgs(result));
    }
}

public class CalculationEventArgs : EventArgs
{
    public int Result { get; }
    public CalculationEventArgs(int result) => Result = result;
}

Subscribe and use it:


var calc = new Calculator();
calc.CalculationPerformed += (sender, e) =>
{
    Console.WriteLine($"Operation result: {e.Result}");
};
calc.Add(10, 20);
// Prints: Operation result: 30

That's the whole "standard": the handler always accepts the sender object and an arguments object — universal and clear.

5. Useful nuances

Encapsulating event invocation: a good practice

In .NET it's common to put event-raising logic into a protected method prefixed with On:


protected virtual void OnCalculationPerformed(CalculationEventArgs e)
{
    CalculationPerformed?.Invoke(this, e);
}

And inside logic ("Add", "Subtract", etc.) you just call that method:


public void Add(int a, int b) => OnCalculationPerformed(new CalculationEventArgs(a + b));

This style allows subclasses to override event behavior and reduces the chance of forgetting to raise the event.

Diagram: How a .NET-standard event is structured

graph LR
    A[Publisher object] -- "event EventHandler/ EventHandler<TEventArgs>" --> B[Subscriber list]
    B -- "Handler method (object sender, EventArgs e)" --> C[Reaction to event]
    A -- "this (sender)" --> C
    A -- "EventArgs (data)" --> C

Comparing event declaration options

Approach Passes initiator (sender) Passes arguments Generality Use in .NET
public event Action<int> MyEvent;
No Yes Low No
public delegate void MyHandler(object, int); event MyHandler ...
Yes Yes Medium No (rare)
public event EventHandler;
Yes No (EventArgs) High Yes, standard
public event EventHandler<MyArgs>;
Yes Yes (MyArgs) Very high Yes, standard

Practical value in interviews and production code

Using the standard event pattern is a must-have for a .NET developer. In an interview you'll almost certainly be asked about EventHandler and the pair sender/EventArgs. An event typed as Action<T> is often perceived as a "simplified" shortcut.

In real projects this approach simplifies teamwork, testing, extensibility and maintenance. Third-party libraries (logging, profilers) integrate more easily when the standard form is used.

Flowchart of raising an event

flowchart TD
    subgraph A[Publisher class]
        C1((Object))
        C2[Method that raises the event]
        C3["event EventHandler<MyArgs>"]
    end
    subgraph B[Subscriber class]
        D1((Subscription))
        D2["Event handler (object sender, MyArgs args)"]
    end
    C1 -- Calls --> C2
    C2 -- "Invoke(this, args)" --> C3
    C3 -- "Notifies" --> D1
    D1 -- "Calls" --> D2

6. Tips, nuances and common mistakes

1. Handler signature. Tempted to make the event type Action<int>? It's tempting, but you lose sender and compatibility with the ecosystem.

2. Passing arguments. Don't confuse EventArgs with ordinary parameters. Put all data for the handler into the arguments object.

3. Using null for arguments. Instead of null use EventArgs.Empty if there is no extra data.

4. Weak typing. Don't make one "catch-all" event EventHandler and stuff everything into it. Create a separate derived class for each event — readability and reliability go up.

5. Mistakes when raising events. Always check for subscribers: SomeEvent?.Invoke(this, e). Without subscribers the event reference is null.

6. Breaking encapsulation. Don't raise the event from outside the publisher class. The event is only for subscribe/unsubscribe; do the raising inside via an On... method.

2
Task
C# SELF, level 52, lesson 3
Locked
Creating an Event with EventHandler<TEventArgs> and Custom EventArgs
Creating an Event with EventHandler<TEventArgs> and Custom EventArgs
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION