CodeGym /Courses /C# SELF /Common mistakes with delegates and events

Common mistakes with delegates and events

C# SELF
Level 54 , Lesson 0
Available

1. Introduction

Working with delegates and events in C# is pleasant and convenient — the language does a lot for you. But behind that curtain there are many pitfalls: invisible memory leaks, weird bugs from double subscription, handler desynchronization and even sudden exceptions during notification broadcasting. Delegates and events are powerful but require careful handling of object lifetimes, understanding of threading and knowledge of how invocation and unsubscription actually work. If your events work "almost always" but sometimes don't fire or cause strange errors — you're definitely not alone! Let's figure out where even experienced developers most often miss things and how to avoid it.

Table of common mistakes

Mistake Consequence How to avoid
Double subscription Handler is called multiple times Keep track of subscriptions, remove before adding
Not unsubscribing (memory leak) Subscribers stay in memory, “zombies” Always unsubscribe, use IDisposable
Exception in a handler Remaining handlers won't be called try/catch in handlers or iterate manually
Modifying subscribers during event Skipping or duplicate calls Iterate a copy of handlers (GetInvocationList())
Calling event when MyEvent == null NullReferenceException Check for null, use ?.Invoke
Handler signature mismatch Compilation error Check signatures
Invoking event from outside Compilation error Invoke only via OnEventName
Static events in the wrong place Subscription mixing Don't make static without a good reason
Closure issues with lambdas Unexpected values Make a copy of the variable

2. Multiple subscription and multiple invocations

The essence of the mistake

If you subscribe the same handler to an event several times, each += adds your method to the delegate invocation list. As a result the handler will be called as many times as it was added.

How does this show up?

Imagine you have a button and a click handler:

Button btn = new Button();
btn.Click += OnButtonClick; // We subscribe
btn.Click += OnButtonClick; // And here's the duplicate subscription!

Now on each button click the OnButtonClick method will be called twice. If inside the handler you update a counter or append a log entry, you'll see doubled results.

How to fix it?

Usually duplicate subscription happens because of code structure issues — for example, when += is placed in a method that gets called multiple times (in different lifecycle phases).

  • Keep track of where subscriptions happen.
  • Don't put += into lifecycle steps that can run repeatedly.
  • Sometimes it's useful to use a "unique" subscription — remove the handler before adding it:
myEvent -= MyHandler; // Remove just in case
myEvent += MyHandler; // Then subscribe again

This is safe: if the handler wasn't there, -= does nothing.

3. Zombie subscriber

The essence of the mistake

If a subscriber subscribes to a long-lived publisher and doesn't unsubscribe, the garbage collector can't collect it: the publisher still holds a reference to the handler delegate, and therefore to the whole subscriber object. Result — memory leaks.

Typical example

public class TemporaryPopup : IDisposable
{
    private Window _hostWindow;
    public TemporaryPopup(Window window)
    {
        _hostWindow = window;
        _hostWindow.Closed += OnHostClosed;
    }

    private void OnHostClosed(object sender, EventArgs e)
    {
        // ...
    }

    public void Dispose()
    {
        _hostWindow.Closed -= OnHostClosed; // Don't forget to unsubscribe!
    }
}

If you forget about Dispose() or don't call it, then even if you remove all references to TemporaryPopup, the object won't be collected — the window still references its handler.

How to avoid it?

  • Implement IDisposable in subscribers if their lifetime is shorter than the publisher's.
  • Use the using pattern or call Dispose() explicitly:
using (var popup = new TemporaryPopup(mainWindow))
{
    // ...
} // Dispose is called here automatically

In GUI apps — unsubscribe when a window/form closes (for example, in close handlers or in the form's Dispose()).

4. Handling exceptions inside handlers

The essence of the mistake

When an event invokes dozens of handlers and one throws, the remaining handlers won't execute.

Demonstration

public event EventHandler MyEvent;

public void Raise()
{
    MyEvent?.Invoke(this, EventArgs.Empty);
}

If one of the methods subscribed to MyEvent throws, the others won't be called — the invocation chain is broken.

How to deal with such errors?

  • In event handlers either handle exceptions locally (try/catch) or knowingly let them bubble.
  • In complex scenarios — iterate subscribers manually and isolate each error:
var handlers = MyEvent?.GetInvocationList();
foreach (var handler in handlers)
{
    try
    {
        handler.DynamicInvoke(this, EventArgs.Empty);
    }
    catch (Exception ex)
    {
        // Logging, emergency recovery
    }
}

5. Changing the subscriber list during event broadcasting

The essence of the mistake

If a handler unsubscribes itself or others inside the event, it can disturb the invocation order: some handlers will be skipped or called multiple times.

How to avoid it?

  • Don't modify subscriptions from within handlers.
  • If needed — iterate a copy of the delegate list:
var handlers = MyEvent?.GetInvocationList();
foreach (EventHandler handler in handlers)
{
    handler(this, EventArgs.Empty);
}

6. Confusion with a null delegate (no subscribers)

The essence of the mistake

If nobody is subscribed, the event delegate equals null. Invoking it without a check leads to NullReferenceException.

Poor example

public event EventHandler MyEvent;

public void Raise()
{
    MyEvent(this, EventArgs.Empty); // If there are no subscribers — exception!
}

How to do it correctly?

  • Use safe invocation: MyEvent?.Invoke(this, EventArgs.Empty).
  • Or use the classic thread-safe pattern: copy the delegate to a local variable and invoke it.

7. Mixing delegates/events of different signatures

The essence of the mistake

Delegates are strongly typed. A mismatch between a handler's signature and the event's delegate is a compile error.

Example

public event EventHandler<string> TextChanged;

void WrongHandler(object sender, int number) { /* ... */ }

TextChanged += WrongHandler; // Compile error!

Use standard delegates EventHandler and EventHandler<T>, and ensure exact signature match.

8. Trying to invoke an event outside the publisher class

The essence of the mistake

event is an encapsulated delegate: external code can't invoke it directly (only add/remove handlers).

Example

public class MyPublisher
{
    public event EventHandler SomethingHappened;
}

var publisher = new MyPublisher();
publisher.SomethingHappened?.Invoke(publisher, EventArgs.Empty); // Error!

How to do it right?

Invoke via a protected/public method on the publisher — usually OnEventName. From outside — only +=/-=.

9. Mistakes with add and remove accessors

The essence of the mistake

Custom accessors for events let you control subscription, but it's easy to break correct invocation ordering or thread-safety.

Example

public event EventHandler MyEvent
{
    add { /* ... */ }
    remove { /* ... */ }
}

If you're not sure — use the default event implementation. If you implement custom accessors, consult docs and consider synchronization.

10. Accessing events from static and instance contexts

The essence of the mistake

It's easy to accidentally declare an event static where it should be instance-level. Then all objects share the same subscriber list.

Example

public static event EventHandler GlobalEvent; // Oops!
// Instances lose individuality, subscriptions merge into a common pile

How to avoid?

Make an event static only when you actually need a global-level event (for example, a global log). Otherwise keep events on the instance level.

11. Capturing variables in lambda expressions

The essence of the mistake

Lambdas capture variables by reference. In loops this often leads to the "last value" problem.

Example

for (int i = 0; i < 5; i++)
{
    button.Click += (s, e) => Console.WriteLine(i);
}
// All handlers will print "5"

How to do it correctly?

for (int i = 0; i < 5; i++)
{
    int copy = i; // Local copy
    button.Click += (s, e) => Console.WriteLine(copy);
}

12. Mixing weak and strong references: Advanced “Weak Events”

In large apps (for example, WPF) a «weak events» mechanism is used where the publisher keeps weak references to subscribers so as not to prevent garbage collection. Weak events help prevent leaks, but a subscriber can be collected and miss the event.

Details: Weak Event Patterns (MSDN)

13. Lack of naming and signature standards for events

The essence of the mistake

Follow standard signatures and names: events — in past tense (Changed, Closed, Completed), arguments — inherit from EventArgs.

“Wrong” example:

public delegate void SomethingHappens(int what);
// ...
public event SomethingHappens Something;

“Right” example:

public event EventHandler<EventArgs> SomethingHappened;

For your own events almost always use EventHandler or EventHandler<T>. Your colleagues (and future you) will thank you.

2
Task
C# SELF, level 54, lesson 0
Locked
Exception Handling in Event Handlers
Exception Handling in Event Handlers
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION