Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does C# require you to write a null check every time you fire an event?

Tags:

c#

null

events

This seems odd to me -- VB.NET handles the null check implicitly via its RaiseEvent keyword. It seems to raise the amount of boilerplate around events considerably and I don't see what benefit it provides.

I'm sure the language designers had a good reason to do this.. but I'm curious if anyone knows why.

like image 861
Billy ONeal Avatar asked Jun 23 '10 15:06

Billy ONeal


3 Answers

It's certainly a point of annoyance.

When you write code which accesses a field-like event within a class, you're actually accessing the field itself (modulo a few changes in C# 4; let's not go there for the moment).

So, options would be:

  • Special-case field-like event invocations so that they didn't actually refer to the field directly, but instead added a wrapper
  • Handle all delegate invocations differently, such that:

    Action<string> x = null; x(); 

    wouldn't throw an exception.

Of course, for non-void delegates (and events) both options raise a problem:

Func<int> x = null; int y = x(); 

Should that silently return 0? (The default value of an int.) Or is it actually masking a bug (more likely). It would be somewhat inconsistent to make it silently ignore the fact that you're trying to invoke a null delegate. It would be even odder in this case, which doesn't use C#'s syntactic sugar:

Func<int> x = null; int y = x.Invoke(); 

Basically things become tricky and inconsistent with the rest of the language almost whatever you do. I don't like it either, but I'm not sure what a practical but consistent solution might be...

like image 127
Jon Skeet Avatar answered Nov 15 '22 00:11

Jon Skeet


we usually work around this by declaring our events like this:

public event EventHandler<FooEventArgs> Foo = delegate { };

this has two advantages. The first is that we don't have check for null. The second is that we avoid the critical section issue that is omnipresent in typical event firing:

// old, busted code that happens to work most of the time since
// a lot of code never unsubscribes from events
protected virtual void OnFoo(FooEventArgs e)
{
    // two threads, first checks for null and succeeds and enters
    if (Foo != null) {
        // second thread removes event handler now, leaving Foo = null
        // first thread resumes and crashes.
        Foo(this, e);
    }
}

// proper thread-safe code
protected virtual void OnFoo(FooEventArgs e)
{
     EventHandler<FooEventArgs> handler = Foo;
     if (handler != null)
         handler(this, e);
}

But with the automatic initialization of Foo to an empty delegate, there is never any checking necessary and the code is automatically thread-safe, and easier to read to boot:

protected virtual void OnFoo(FooEventArgs e)
{
    Foo(this, e); // always good
}

With apologies to Pat Morita in the Karate Kid, "Best way to avoid null is not have one."

As to the why, C# doesn't coddle you as much as VB. Although the event keyword hides most of the implementation details of multicast delegates, it does give you finer control than VB.

like image 42
plinth Avatar answered Nov 14 '22 23:11

plinth


You need to consider what code would be required if setting up the plumbing to raise the event in the first place would be expensive (like SystemEvents) or when preparing the event arguments would be expensive (like the Paint event).

The Visual Basic style of event handling doesn't let you postpone the cost of supporting such an event. You cannot override the add/remove accessors to delay putting the expensive plumbing in place. And you cannot discover that there might not be any event handlers subscribed so that burning the cycles to prepare the event arguments is a waste of time.

Not an issue in C#. Classic trade-off between convenience and control.

like image 27
Hans Passant Avatar answered Nov 15 '22 01:11

Hans Passant