How do C# Events work behind the scenes?

I’ve written this up in a fair amount of detail in an article, but here’s the summary, assuming you’re reasonably happy with delegates themselves:

  • An event is just an “add” method and a “remove” method, in the same way that a property is really just a “get” method and a “set” method. (In fact, the CLI allows a “raise/fire” method as well, but C# never generates this.) Metadata describes the event with references to the methods.
  • When you declare a field-like event (like your ElementAddedEvent) the compiler generates the methods and a private field (of the same type as the delegate). Within the class, when you refer to ElementAddedEvent you’re referring to the field. Outside the class, you’re referring to the field.
  • When anyone subscribes to an event (with the += operator) that calls the add method. When they unsubscribe (with the -= operator) that calls the remove.
  • For field-like events, there’s some synchronization but otherwise the add/remove just call Delegate.Combine/Remove to change the value of the auto-generated field. Both of these operations assign to the backing field – remember that delegates are immutable. In other words, the autogenerated code is very much like this:

    // Backing field
    // The underscores just make it simpler to see what's going on here.
    // In the rest of your source code for this class, if you refer to
    // ElementAddedEvent, you're really referring to this field.
    private EventHandler<EventArgs> __ElementAddedEvent;
    
    // Actual event
    public EventHandler<EventArgs> ElementAddedEvent
    {
        add
        {
            lock(this)
            {
                // Equivalent to __ElementAddedEvent += value;
                __ElementAddedEvent = Delegate.Combine(__ElementAddedEvent, value);
            }
        }
        remove
        {
            lock(this)
            {
                // Equivalent to __ElementAddedEvent -= value;
                __ElementAddedEvent = Delegate.Remove(__ElementAddedEvent, value);
            }
        }
    }
    
  • The initial value of the generated field in your case is null – and it will always become null again if all subscribers are removed, as that is the behaviour of Delegate.Remove.

  • If you want a “no-op” handler to subscribe to your event, so as to avoid the nullity check, you can do:

    public EventHandler<EventArgs> ElementAddedEvent = delegate {};
    

    The delegate {} is just an anonymous method which doesn’t care about its parameters and does nothing.

If there’s anything that’s still unclear, please ask and I’ll try to help!

Leave a Comment