Events get a little overhaul in C# 4, Afterward: Effective Events

In Parts I, II, and III, I talked about the slight changes that we made to field like events, to the += and -= event accessor operators, and how this may affect you. Just so we’re all on the same page, I want to consolidate here a list of Dos and Don’ts so you know how to use these language features effectively and safely.

As with any list of recommendations someone gives you for your source code, please feel free to adapt them or ignore them if your particular set of requirements is in conflict with mine. I am really interested here in field-like events; if you’re implementing your own event accessors, then you know better than I do what you need.

The event pattern

For the following, suppose that you want a public event of type EventHandler. EventHandler is a delegate type that takes a “sender” object and some “args,” and it’ll serve as our example here, but it’s just a stand-in. Use any delegate you like. Anyway, the standard event pattern is the following:

 public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
    EventHandler myEvent = MyEvent;
    if (myEvent != null)
    {
        myEvent(this, e);
    }
}

There are a few important details here:

  1. This is exactly the same as the event pattern has always been. If you implemented C# 3 events this way, you’re golden.
  2. It’s important that you copy the backing delegate field to a local in the “On...” method (see Synchronization below).
  3. These members can have any accessibility you like, though a private event would be weird.

Synchronization

Never use “lock(this)” for any reason, especially not to synchronize any action you take on your events. You’ve probably heard enough of this by now, but in C# 3 this is unwise and in C# 4 it’s wrong. If you have an invocation of the delegate under a “lock(this)”, that’s especially awful, as it violates the idea that you should do as little as possible while holding a lock (you’re calling arbitrary event handlers!) and opens the door to deadlock. Instead...

Reads

If you want to read the value of your event delegate, it is safe to simply do so. If you test it and take further action on it, as in the “On...” method, then you should copy it to a local first. Otherwise there may be a mutation between the reads. Remember, delegates are immutable, and when the event delegate changes it’s just the reference that’s changing. Therefore all of your reads are effectively snapshots in time, and consecutive reads will represent intervening mutations. (Note: if it’s not clear, calling the delegate is a “read” operation because we’re talking about reading the reference).

Read-modify-writes

This is the purpose of event accessors, which += and -= allow access to. If you want to look at the event delegate, do something with it, and write the result back, you should use += and -=, which means you are limited to Combine and Remove. If these don’t suffice, you should probably consider implementing your own accessors and not using field-like events. Although it is possible to mimic the compare-and-swap that the C# 4 accessors provide, I don’t recommend it and I won’t put the code here; it’s very easy to get wrong.

Writes

If you want to write to the delegate without using any information about its current state, please do. The only reason I can imagine that one might do this is to say

 myEvent = null;

in order to reset everything, but even in this case you need to be prepared that a client could call your add handler immediately following the reset.

Other

It’s kind of weird that this much attention has been paid to the safety of the event delegate via the accessors, given that your “On...” method is going to invoke the delegate on whatever thread it pleases. But precisely how the handlers are called (on what thread) is left as a matter of a contract between you and your clients. Be careful about that. Furthermore, that contract really ought to specify that the handlers should not throw—error recovery and multicast delegates don’t go together well. But again, this is a matter to settle between you and your clients.

Of course, if you have special knowledge that no one on any other thread can possibly call your accessors (e.g., you are in your constructor), then feel free to do whatever you want with your delegate field.

Single-threaded events

If you are operating in a single threaded environment, and are somehow encumbered by the synchronization that field-like events provide and which you determined that you do not want, write your own event as follows:

 private EventHandler myEventField;
public event EventHandler MyEvent
{
    add { myEventField += value; }
    remove { myEventField -= value; }
}
protected void OnMyEvent(EventArgs e)
{
    if (myEventField != null)
    {
        myEventField(this, e);
    }
}

No-op Events

If you are required to implement an event that appears on an interface, but you never intend to call it, use this pattern for an empty event implementation.

 interface I
{
    event EventHandler MyEvent;
}

class C
{
    event EventHandler I.MyEvent
    {
        add { }
        remove { }
    }
}

Virtual events

Never override a virtual event with a field-like event. The compiler does not handle this gracefully, and it never has. If you need to override virtual events, write your own handlers.

 

Events and co- and contravariance

Never expose an event of generic delegate type when that delegate type is covariant or contravariant in any of its type parameters. Examples of such delegates include all of the generic System.Actions and System.Funcs.

The problem is that in .NET 4, Delegate.Combine and Delegate.Remove will not work properly when two delegate instances have different actual types. This means that generally, multicast delegates of these “variant” types are dicey, and events in particular expose the Combine and Remove functionality pretty directly. When I say “will not work properly,” I mean they’ll throw.

By the way, the generic EventHandler, EventHandler<T>, is not contravariant in T precisely because of this limitation.

This may be remedied in future versions of the framework, but I am not on the team that owns delegates in the runtime and I don’t represent them, so I can’t make any promises.

 

 

And finally…

Sorry if this post is dry! I mostly want to make sure it’s up here for easy searchability. No more of this event stuff next time; I’m planning on writing a little more about dynamic.

And a note to those of you from the comments who want to hear about weakly referenced event handlers: perhaps later. There are some tricks to play, but also some fundamental incompatibilities with the way events and delegates are represented in .NET. But I hear you.