Events get a little overhaul in C# 4, Part II: Semantic Changes and +=/-=

Last time, we saw that the field-like event accessor code that the C# compiler emits has changed. And it’s better in a few ways because we’ve banished the locking code that we used to emit.

There are a few questions that came up in the comments, and two of them in particular are probably as straightforward as can be when you’re thinking about code that the compiler’s generating. First, is the code bigger? And second, is it slower? In this case, yes and no. Yes, the IL is 17 bytes bigger per accessor (not per instance), which will make your assembly that much bigger. Which is not fantastic news, but hey, 17 bytes. And no, the code is not slower. In fact, it’s faster! In tests that I’ve performed, when the delegate operations are as simple as possible the accessors appear to be about 20% to 25% faster. That’s because there’s no lock.

But neither of these issues are cause for concern or celebration. No, the big problem is that this change is an observable semantic difference between C# 3 and C# 4, and there is existing code that it could break. You see, you used to be able to protect against any change to your events over some block of code simply by putting the code in a “lock(this)”:

 class OldClass
{
  public event EventHandler E;
  public void UseE()
  {
    lock (this)
    {
      // Safe in C# 3, unsafe in C# 4!
      E = (EventHandler)Delegate.Combine(E, new EventHandler(SecretHandler));
    }
  }
  private void SecretHandler(object sender, EventArgs e) { }
}

This code used to be effective, because inside the lock, we could be sure that no other thread would be changing the value of E between our read and our write. So this could not screw up E. Now, uh oh, it can! But just a minute, who writes code like this? Anyone who is going to call Delegate.Combine is probably going to do so with the + operator in C#:

 class OldClass
{
  public event EventHandler E;
  public void UseE()
  {
    lock (this)
    {
      // Still safe in C# 3, unsafe in C# 4!
      E = E + SecretHandler;
    }
  } 
  private void SecretHandler(object sender, EventArgs e) { }
}

That calls Delegate.Combine in the same exact way. And actually, I suspect that anyone who writes that code is really going to write the following:

 class OldClass
{
  public event EventHandler E;
  public void UseE()
  {
    lock (this)
    {
      E += SecretHandler;
    }
  } 
  private void SecretHandler(object sender, EventArgs e) { }
}

That code also calls Delegate.Combine, and we broke it too! This might be surprising to you. Look at it, the thing on the left of the += is an event, so surely this calls the event accessor, right? Nope! Let me take a quick detour here and explain to you how the binding of += works in C#. There are two possibilities: (1) either there is an actual + operator, such as with ints, and x += y binds to “x = x + y” except that x is only evaluated once. This is the compound assignment operator; or (2) the thing on the left hand side is an event, and x.E += y binds to “x.add_E(y)”. This is the event accessor operator, and in fact this is the only way to bind to an event accessor. So what do we have in the snippet above? Well, the extra bit of detail that you need to decide is the following rule about field-like events in C#: outside of the class or struct that defines a field-like event E, binding to the name E resolves to the event itself, on which the only legal operation is calling an accessor; inside the class or struct that defines a field-like event E, binding to the name E resolves to the private delegate field.

So in that last bit of code there, in “E += SecretHandler,” E is actually the delegate E, which has a +, not the event! So the binding you get is to Delegate.Combine (via +), not to the add accessor. And therefore, the explicit lock is the only thing that makes this safe in C# 3. Oh my! What have we done, and how can we fix it?

Well, it turns out that the binding in that last snippet of code is very surprising to most C# developers who have encountered it. It’s so surprising that I would say that in 99% of the cases, the developer thinks they are calling the accessor and so they don’t even put the lock in:

 class OldClass
{
  public event EventHandler E;
  public void UseE()
  {
    // Unsafe in C# 3!
    E += SecretHandler;
  }
  private void SecretHandler(object sender, EventArgs e) { }
}

But you cannot call the event accessor from inside the class that defines it. There’s a simple fix, though. Now we let you call the add accessor this way!

I hope I haven’t lost you, since this is actually quite subtle and you probably were unaware of the binding rule I outlined above. But that’s the rule that we had in C# 3. In C# 4, however, the new rule is that inside your class with field-like event E, the name E binds to the backing delegate field (so you can call it, for instance), except when it’s the left hand side of a += or –=, in which case it becomes the event.

This change fixes the code in C# 3, which you might not have even known was broken. Heck, even if you were among the small fraction of people who knew that events took care of some synchronization for you, you still probably didn’t know this was broken. I know this because there is a constant slow trickle of people who email us having discovered this the hard way. And we now we fixed them.

But we also fixed, to some degree, the problem with the new C# 4. If you wrote “E += Handler” in your class, now you get the benefit of our new synchronization whether or not you put the “lock(this)” there. Which is great, because you don’t want to write that synchronization yourself.

Ok, ok, there’s more. But I’ll leave it at that until Part III.