Events

Intro

An event is a restricted delegate member that implements publisher-subscriber communication. Outside the declaring type, consumers can only subscribe (+=) and unsubscribe (-=); they cannot invoke or replace the delegate invocation list. This encapsulation is why events are preferred over raw public delegates in APIs.

The standard .NET event signature uses EventHandler or EventHandler<TEventArgs>.

public sealed class PriceFeed
{
    public event EventHandler<PriceChangedEventArgs>? PriceChanged;

    private decimal _price;

    public void UpdatePrice(decimal newPrice)
    {
        if (newPrice == _price) return;
        _price = newPrice;
        OnPriceChanged(new PriceChangedEventArgs(newPrice));
    }

    protected virtual void OnPriceChanged(PriceChangedEventArgs e)
        => PriceChanged?.Invoke(this, e);
}

public sealed class PriceChangedEventArgs : EventArgs
{
    public decimal Price { get; }
    public PriceChangedEventArgs(decimal price) => Price = price;
}

Why event Instead of Public Delegate Field

With a public delegate field, any caller can do dangerous operations like:

event blocks these operations for external code and exposes only subscription semantics.

Custom add and remove

You can define explicit accessors for advanced scenarios (thread-safe collections, weak subscriptions, deduplication):

private EventHandler? _tick;
private readonly object _gate = new();

public event EventHandler Tick
{
    add
    {
        lock (_gate)
            _tick += value;
    }
    remove
    {
        lock (_gate)
            _tick -= value;
    }
}

Pitfalls

  1. Memory leaks via long-lived publishers: subscribers stay alive while subscribed.
  2. Forgotten unsubscribe: common in UI/view-model/service lifetimes.

Example leak-safe subscription pattern:

public sealed class Listener : IDisposable
{
    private readonly PriceFeed _feed;

    public Listener(PriceFeed feed)
    {
        _feed = feed;
        _feed.PriceChanged += OnPriceChanged;
    }

    private void OnPriceChanged(object? sender, PriceChangedEventArgs e)
        => Console.WriteLine(e.Price);

    public void Dispose()
        => _feed.PriceChanged -= OnPriceChanged;
}

Tradeoffs

Questions


Whats next