Also available at

Also available at my website http://tosh.me/ and on Twitter @toshafanasiev

Friday, 4 February 2011

Guarded Event Invocation in C#

When declaring events in C# code I've always used a guarded invocation pattern, as follows:


class SomeClass {

  // ....

  public event EventHandler TaDaa;

  private void FireTaDaa() {
    var t = this.TaDaa;
    if ( t != null ) {
      t.Invoke( this, EventArgs.Empty );
    }
  }

  public void SomeMethod() {

    // ....

    this.FireTaDaa();
  }
}
This is great - having zero subscribers won't cause a NullReferenceException and the caching of the event backing field in the local variable gives cheap implicit thread safety.

Recently though a colleague showed me this pattern:


class SomeClass {

  // ....

  public event EventHandler TaDaa = delegate { };

  // no Fire... method

  public void SomeMethod() {
    
    // ....

    this.TaDaa.Invoke( this, EventArgs.Empty );

  }
}
Which protects you in case there are no subscribers and is every bit as thread safe as the other, more hand-rolled and clunky method. The initialisation of the event backing field with the anonymous, empty delegate means that the event backing field will never be a null reference - no other code has direct access to the dummy subscriber and so cannot remove it from the invocation list of the delegate*.

While the dummy subscriber takes the form of an extra object in memory at runtime - one for each event initialised in this way, these are shared between instances of the class so there is only one extra object per event, per class, per AppDomain so the cost is negligible. Also it saves your code performing the null check and the invocation of the empty dummy subscriber is something that the JIT compiler should be able to optimise out (though I haven't checked this).

All in all I like this, I'm going to adopt it.

*(provided the backing field is only modified using the Delegate.Combine and Delegate.Remove methods - but then the C# compiler enforces this, preventing you from directly accessing the backing field of an event declared as above)