Events get a little overhaul in C# 4, Part III: Breaking Changes


In the last two posts, I revealed that field-like events in C# 4 have a better synchronization story, and that we changed += and -= in a kind of subtle way to protect you, in many cases, from the semantic differences this introduces between C# 3 and C# 4.


Now I’m here to tell you about some more in-your-face breaks that are a lot less subtle, and related to the binding change for += and -=. My hope is that anyone who actually sees these breaks will find this post and learn how to fix them. Let’s go.


Break #1: warning CS0067: The event ‘MyClass.MyEvent’ is never used


So imagine you have the following C# 3 code. It compiles just fine. No warnings, no errors. (Forget the about the actual utility of this piece of code for a moment).

using System;

class MyClass
{
static void Main()
{
MyClass x = new MyClass();
x.MyEvent += () => Console.WriteLine(“Yay!”);
}
public event Action MyEvent;
}


You upgrage to C# 4 and now you get a warning!

test.cs(10,25): warning CS0067: The event ‘MyClass.MyEvent’ is never used

What happened? Well, remember the subtle change in the way we interpreted += from the last post? It affects line 8 above. In C# 3, since this code is inside the class that defines the event, the += was a compound operator. Therefore it was a direct operation on the delegate field that backs MyEvent. In C# 4, however, we’ve changed the interpretation so that the += on line 8 is now calling the add accessor instead.


So why the warning? Because the warning is trying to tell you something about the private backing field, not the event accessors! In C# 4, this class has a delegate field that no one ever uses for anything. No one invokes it, no one passes it to Delegate.Combine, no one does anything with it. Of course the event accessors use it, but compiler generated code doesn’t count for this warning (and it never has).


The first possible fix here is: make use of the event! Perhaps you meant to raise the event, but you never got around to adding the code, and now this warning is actually telling you about a bug. If you were to add an “OnMyEvent” method that invoked MyEvent with “MyEvent()”, then you’ve made use of the field, the warning goes away, and you’ve fixed a bug.


The second fix is to get rid of the event, or at least the backing field. In this case, you don’t actually want to raise the event, and you might as well delete the thing. So you can safely remove the event and all the references to it and all’s well. It didn’t do anything anyway.


Of course, you might want to delete the event but be unable to. This can be the case if some base class or interface that you implement requires you to define the event. In that case, you probably have code like this:

interface IHasSomeEvent
{
event Action SomeEvent;
}

class MyClass : IHasSomeEvent
{
// there is a backing delegate here that goes unused!
public event Action SomeEvent;
}


and my recommendation would be that you change your class to look like this:

class MyClass : IHasSomeEvent
{
// this takes no storage and does nothing!
event Action IHasSomeEvent.SomeEvent { add { } remove { } }
}

This last case, where you are obligated to implement some event that you don’t care about, is the context in which I’ve seen these warning 67’s pop up most frequently. Notice that the fix I recommend actually does nothing more than fulfill the obligation of implementing the interface, and it doesn’t pollute the public surface area of your class with the event name. If you wanted to be especially hard-nosed, you could throw a NotImplementedException from those accessors, but that seems like a bit much to me.


Break #2: error CS0029: Cannot implicitly convert type ‘void’ to ‘System.Action’


This break can also show up as “CS1503: Argument n: cannot convert from ‘void’ to ‘System.Action’” or any of a few different errors where a conversion is required but does not exist. And it comes from code like this:

class MyClass
{
event Action MyEvent;
public void DoSomething()
{
Action a = MyEvent += () => Console.WriteLine(“Yay!”);
a();
}
}

Here, what we’re doing is adding the little Console.WriteLine delegate to my event, and then call the result. This works in C# 3, again, because += binds directly to the event. Therefore, the result of the compound assignment is the new delegate that is now referenced by MyEvent. However, in C# 4, since += is a call to an event add accessor, and the result of an event accessor call is void, you cannot directly look at the result like this. Event accessors are designed this way so that code outside your class can’t break encapsulation and get their hands on your delegates.


This is really weird code. I have never actually seen it in the wild. If you have such code, why not just call the accessor and then call the event?

class MyClass
{
event Action MyEvent;
public void DoSomething()
{
MyEvent += () => Console.WriteLine(“Yay!”);
MyEvent();
}
}
Or, generally, call the accessor and then do whatever you were going to do with the result but with the event instead? The semantics are not 100% the same if you do this (for instance, someone on another thread could have modified the event in the meantime), but if you think that makes a difference to you, then you should re-examine your requirements and possibly implement the event accessors yourself because the original code was wrong (if you left out the lock(this)) or terribly unsafe (if you used lock(this)).

Other meaningless behavioral differences


There are a few other things that are different now that your +=’s are all calls to an accessor. But none that you should spend any time whatsoever worrying about; I list them here only to be thorough.


For instance, there’s another method call here. That could consume extra stack space. If you had just achieved the limit on your stack before, this one extra method call could push you over the edge and give you a StackOverflowException.


Also, there is a theoretical resource starvation problem with the compare and swap lock-free code when two threads sort of “line up” perfectly forever. This will affect you with probability zero.


And you could do silly things, such as: put a SecurityCriticalAttribute on your field-like event accessors (use the “method:” attribute target specifier), purely for the purpose of creating a situation wherein you can run in partial trust and a += in the event-defining class used to work in C# 3 but now it does not. Just… don’t do this.


Next time I’ll conclude this short series by going over the standard event pattern in C# 4, with recommendations about exactly how you should implement and use field-like events, and when you should not.

Comments (4)

  1. Anonymous says:

    Hi Chris,

     What about the breaking changes that compiler won’t detect? I mean lets say someone relied on the implementation of accessors to lock on this?.

    for example someone relied that in the code like this

    lock(this)

    {

      delegateA();

      delegateA();   // or actually any other delegateB()

    }

    Code could have relied on the fact that the delegate doesn’t change while under lock.

  2. Anonymous says:

    @Dmitry:

    Code could have relied on the fact that the delegate doesn’t change while under lock.

    In the code you give, the value of delegate backing the event absolutely can change under lock. Consider:

      class Foo {

          public event Action E1;

          public event Action E2;

          public void Raise() {

             lock (this) {

                E1(); E2();

             }

          }

      }

      Foo foo = new Foo();

      foo.E1 += () => foo.E2 += () => {};

    So handler for E1 changes E2. The reason why this works is this aspect of Monitor.Enter (quoted from MSDN):

    "It is legal for the same thread to invoke Enter more than once without it blocking"

    Locks are to synchronize threads, not to prevent the same thread from accessing resource; so if the thread has already acquired a lock, it will succeed on any nested attempts to do the same.

    So any such code is already broken, in a sense that it does not guarantee something that his author may think it does.

  3. Anonymous says:

    I was not saying that there is no way to change the underlying event.

    I was arguing that there could be a case (another thread in my example) that modifies E1/E2 was not able to do it while thread 1 was holding the lock.

  4. Anonymous says:

    Chris, any idea if there will ever be support for weak delegates in the CLR?

    At the moment there’s no easy way to implement an Observer pattern.  People like IanG have tried to cobble something together and failed.

    The only workarounds seem to require support from the observed object, and require a tremendous about of code.  The WeakEvent pattern in WPF is worthless for any code not pumping a dispatch queue.

    Where are weak delegates?  For that matter, what has the CLR team been doing since 2.0 came out, five years ago?

    There’s been an issue (ID 94154) on MS Connect about this since 2004, and still no word from Microsoft on when (if?) it’s going to be fixed.