Thursday, 12 February 2009

Fun with field-like events

UPDATE: this all changes in 4.0; full details here.

Field-like events; a great compiler convenience, but sometimes a pain. To recap, a field-like event is where you let the compiler write the add/remove methods:

public event EventHandler Foo;

All well and good... the C# compiler creates a backing field, and add/remove accessor methods - however, the C# specification also dictates that the accessor methods will be synchronized. Unfortunately, the ECMA and MS specs disagree how. The ECMA spec maintains that the "how" is unimportant (an implementation detail) - the MS spec dictates "this" for instance methods, "typeof(TheClass)" for static methods. But if you follow the ECMA spec, there is no reliable way of independently using the same lock - you simply can't guarantee what it is (the C# spec doesn't mention [MethodImpl], so using this would also be making assumptions).

Aside: best practice is not to lock on either "this" or a Type - since in both cases you can't guarantee who else might be using the same lock.

Of course, in most cases this is irrelevant. Most classes simply don't need thread safety, and it is pure overkill. However, I was dealing with a case earlier where thread-safety was important (it is a class for simplifying fork/join operations). For convenience, I wanted to provide both regular event accessors and a fluent API to do the same - i.e.

class Bar {
public event EventHandler Foo;
public Bar AddFoo(EventHandler handler) {
Foo += handler;
return this;
}
// snip
}

with fluent-API usage:

    new Bar().AddFoo(handler).Fork(action).Fork(action).Join();

So what broke? The C# spec also dictates that inside the type, all access goes directly to the field. This means that the usage inside the AddFoo method is not synchronized. This is bad. So what can we do? My first thought was to use a nested class (since this is then a different type):

class Bar {
public event EventHandler Foo;
public Bar AddFoo(EventHandler handler) {
BarUtil.AddFoo(this, handler);
return this;
}
static class BarUtil {
internal static void AddFoo(
Bar bar, EventHandler handler)
{
bar.Foo += handler;
}
}
// snip
}

Unfortunately, it turns out (by inspection) that this still uses the field directly, so isn't synchronized. If we make it non-nested, it finally works correctly - but then we're getting into the position where it simplifies things to just have an extension method:

class Bar {
public event EventHandler Foo;
// snip
}
static class BarUtil {
public static Bar AddFoo(
this Bar bar, EventHandler handler)
{
bar.Foo += handler;
return bar;
}
}

As it happens, I decided to just side-step the whole debacle instead and do the locking myself...

Summary: field-like events; unnecessary synchronization when you don't need thread-safety, and highly questionable synchronization when you do need it...