CodeGym /Courses /C# SELF /Detailed breakdown of subscription (

Detailed breakdown of subscription ( +=)

C# SELF
Level 53 , Lesson 0
Available

1. Introduction

Events in C# are not just variables you can assign a delegate to. They're a protected list of handlers, and only the event owner can trigger their execution; others can only add (+=) or remove (-=) their reactions.

For example, you can subscribe to an event like this:

worker.WorkCompleted += Worker_WorkCompleted;

At first glance this looks like normal addition, but it actually works differently. Under the hood the event keeps an invocation list — a set of delegates to call when the event fires. When you write +=, a new handler is appended to that list.

Let's dig into what happens inside, how that list is formed, and what nuances can arise when subscribing.

What is a delegate-chain?

Remember, delegates in C# are "multicast": you can assign multiple methods to them and they'll all run in order when the delegate is invoked. Events use this mechanism: their value is essentially a delegate with a list of handlers.

In code terms:

public event EventHandler<WorkCompletedEventArgs> WorkCompleted;

When someone subscribes:

worker.WorkCompleted += MyHandler;

Under the hood C# does roughly this:

  • Takes the current delegate (the handler list).
  • Calls Delegate.Combine on it (merges handlers).
  • Writes the updated chain back to the event field.

Schematically:

Operation Internal event handler list
Before null or [Handler1]
After += Handler2 [Handler1, Handler2]
After another += H3 [Handler1, Handler2, Handler3]

Fun fact: the multicast delegate mechanism isn't "magic" — it's a concrete implementation: a delegate internally holds an array of methods to call.

2. How subscription works: plain explanation

Let's look at a concrete example. Say we have a publisher (Worker) and subscribers (Logger, Notifier):

public class Worker
{
    public event EventHandler<WorkCompletedEventArgs> WorkCompleted;

    public void DoWork()
    {
        // ... work ...
        OnWorkCompleted("Zadanie zaversheno!");
    }

    protected virtual void OnWorkCompleted(string message)
    {
        WorkCompleted?.Invoke(this, new WorkCompletedEventArgs { Message = message });
    }
}

public class Logger
{
    public void LogWorkCompleted(object? sender, WorkCompletedEventArgs e)
    {
        Console.WriteLine("Log: " + e.Message);
    }
}

public class Notifier
{
    public void ShowNotification(object? sender, WorkCompletedEventArgs e)
    {
        Console.WriteLine("Uvedomlenie: " + e.Message);
    }
}

In Main:

var worker = new Worker();
var logger = new Logger();
var notifier = new Notifier();

worker.WorkCompleted += logger.LogWorkCompleted;
worker.WorkCompleted += notifier.ShowNotification;

// Start work
worker.DoWork();

When OnWorkCompleted is invoked, the event first calls logger.LogWorkCompleted, then notifier.ShowNotification (in the order you subscribed).

3. Useful nuances

Visualization: how an event stores handlers

+---------------------+
| Worker              |
|---------------------|
| WorkCompleted Event |    ---> [ LogWorkCompleted, ShowNotification ]
+---------------------+

On subscription each new handler "attaches" to the existing list. When the event is raised the delegate calls all subscribed methods in sequence.

Multiple subscriptions of the same method

worker.WorkCompleted += logger.LogWorkCompleted;
worker.WorkCompleted += logger.LogWorkCompleted; // Twice!

In that case the handler will be called as many times as it is subscribed — so here twice in a row.

Subscribing with a lambda

worker.WorkCompleted += (sender, e) => Console.WriteLine("Anonym handler: " + e.Message);

If you subscribe the same lambda multiple times — same deal, it will be invoked multiple times per event. But be careful: each lambda creates its own delegate object, so you can't unsubscribe it easily (see lecture 260 and beyond for details).

How subscription works internally: low-level breakdown

An event is a special property with two accessors (add/remove) that run when += and -= are used. Simplified, the compiler generates roughly this code:

// Roughly like this (simplified)
public event EventHandler<WorkCompletedEventArgs> WorkCompleted
{
    add { /* handler add code */ }
    remove { /* handler remove code */ }
}

By default a standard implementation is used: delegates are combined with Delegate.Combine and removed with Delegate.Remove.

This protects the event: you can't invoke the event from outside (no worker.WorkCompleted(...);), you can only subscribe or unsubscribe.

Subscription mechanics: draw the diagram

             +----------------------+
             |                      |
             v                      v
    +--------------------+   +----------------------+
    | LogWorkCompleted   |   | ShowNotification    |
    +--------------------+   +----------------------+
             ^                      ^
             \______________________/
                       ^
                       |
               WorkCompleted Event

The so-called "Invocation List" — the chain of calls.

Why this matters: practical significance

Understanding subscription mechanics is key to managing relationships between objects. You can build systems where components dynamically subscribe and unsubscribe to events without creating tight coupling. This is standard in UI frameworks, game engines, server apps and even in modern microservice architectures (there it's on the level of queues, but the idea is the same).

In interviews you're often asked how C#'s event model works, why events make a system flexible, and how to properly manage subscription lifecycle.

4. What you can (and can't) do from outside the class

Allowed

  • Subscribe to an event (+=)
  • Unsubscribe (-=)

Not allowed

  • Invoke the event directly
  • Assign a delegate to the event directly (worker.WorkCompleted = ... — error!)

These restrictions are enforced by the event keyword. If you declared the delegate as a plain field:

public EventHandler<WorkCompletedEventArgs> WorkCompleted; // ne event!

— anyone could do everything, including zeroing out handlers, which would create chaos and potential bugs. That's why you almost always use only the event keyword!

Can one method subscribe to multiple events?

Yes! This is called "multisubscribe". For example:

worker.WorkCompleted += logger.LogWorkCompleted;
anotherWorker.WorkCompleted += logger.LogWorkCompleted;

If you subscribed the same method to events of different objects, the handler will fire for both events. Inside the handler you can tell who raised the event by the sender parameter.

Real case: dynamic subscriptions

Imagine an app where the user can start multiple tasks in parallel. For each new task a Worker object is created, and the same handler is subscribed to its event — e.g., the logger method logger.LogWorkCompleted.

var allWorkers = new List<Worker>();
for (int i = 0; i < 10; i++)
{
    var w = new Worker();
    w.WorkCompleted += logger.LogWorkCompleted;
    allWorkers.Add(w);
}

As a result, when any task finishes, the logger will get notified and will record what and when happened.

Practical note: how to see event subscribers

In normal code you can't directly know how many handlers are subscribed to an event (the event encapsulates the delegate). But inside the publisher class you can work with the event delegate directly, e.g. inspect its GetInvocationList():

// Only inside the publisher class
var handlers = WorkCompleted?.GetInvocationList();
if (handlers != null)
    Console.WriteLine($"Podpischikov: {handlers.Length}");

This is useful if you need custom dispatch logic or debugging (though it's better to use this only for learning purposes!).

5. Common mistakes and specifics

Handler not added? It won't be called!
If you didn't subscribe, the handler will never be invoked. Always check for null before raising the event (otherwise you'll get a NullReferenceException).

Multiple subscriptions
If you subscribed multiple times to the same event with the same method — the handler will run that many times. This is sometimes a trap: an accidental duplicate += turns one notification into multiple identical ones.

Static/instance methods
You can subscribe both static and instance methods. The important thing is the correct signature.

public static void StaticHandler(object? sender, WorkCompletedEventArgs e) { /* ... */ }
worker.WorkCompleted += StaticHandler;

Lambdas and subscribing inside a loop
If you, for example, subscribe lambdas inside a loop, make sure you understand what happens to variables the lambda captures. You can accidentally capture a different value than you expected.

2
Task
C# SELF, level 53, lesson 0
Locked
Dynamic Subscription and Viewing Subscribers
Dynamic Subscription and Viewing Subscribers
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION