tags:

views:

395

answers:

7

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.

+3  A: 

Don't really know why is this done, but there's a variation of a Null Object pattern specifically for delegates:

private event EventHandler Foo = (sender, args) => {};

This way you can freely invoke Foo without ever checking for null.

Anton Gogolev
`public event EventHandler Foo = delegate {};` is cleaner IMO. Although I personally dislike this pattern.
Matt Greer
+3  A: 

Because RaiseEvent carries a some overhead.

There's always a tradeoff between control and ease of use.

  • VB.Net: ease of use,
  • C#: more control as VB
  • C++: even more control, less guidance, easier to shoot yourself in the foot
GvS
@GvS: What overhead? Checking for `null`? That's certainly much less than a method call...
Billy ONeal
I was thinking about the Handles syntax of VB.Net. But you are right, not a lot, edited.
GvS
+17  A: 

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...

Jon Skeet
Quite frankly I see it as a *very* minor annoyance. I much prefer a exception over silent errors.
ChaosPandion
Although, sometimes when I see *"frameworks"* throwing lots of `NullReferenceExceptions` my resolve weakens.
ChaosPandion
Inconsistancies abound. I still can't call `Nullable<T>.HasValue` without thinking "you can't access a property on a value that could be null!"
Jeffrey L Whitledge
Events can be non-void? That's a new one for me.
Billy ONeal
@Billy: See AppDomain.AssemblyResolve for an example.
Jon Skeet
+11  A: 

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.

plinth
+1: Or more convincingly if you initial the event to a delegate that does something practical (if the scenario necessitates that of course) then you would never need to do the check. Good point about avoiding the race condition in threading scenarios though. This is the best answer in my opinion...even better than mine :)
Brian Gideon
It's all fun and games until you try to convert the above into Visual Basic, and find it doesn't support anonymous delegates...!
SLC
@SLC: That's less of a problem for Visual Basic though, because the RaiseEvent keyword handles the `null` check correctly.
Billy ONeal
Note that this technique only eliminates *one* of the two race conditions in your code. This never dereferences null, to be sure, but it is still possible for one thread to remove an event handler and destroy state that it needs, and a second thread to call the removed event handler, which then crashes because its required state is missing.
Eric Lippert
A: 

The reason really boils down to C# giving you more control. In C# you do not have to do the null check if you so choose. In the following code MyEvent can never be null so why bother doing the check?

public class EventTest
{
  private event EventHandler MyEvent = delegate { Console.WriteLine("Hello World"); }

  public void Test()
  {
    MyEvent(this, new EventArgs());
  }
}
Brian Gideon
Is there any reason they couldn't have done *both*? This seems a common enough PITA to me that I'm surprised there isn't an alternate syntax which eschews the explicit null check.
Billy ONeal
+2  A: 

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.

Hans Passant
+2  A: 

Extension methods provide a very cool way, to get around this. Consider the following code:

static public class Extensions
{
    public static void Raise(this EventHandler handler, object sender)
    {
        Raise(handler, sender, EventArgs.Empty);
    }

    public static void Raise(this EventHandler handler, object sender, EventArgs args)
    {
        if (handler != null) handler(sender, args);
    }

    public static void Raise<T>(this EventHandler<T> handler, object sender, T args)
        where T : EventArgs
    {
        if (handler != null) handler(sender, args);
    }
}

Now you can simply do this:

class Test
{
    public event EventHandler SomeEvent;

    public void DoSomething()
    {
        SomeEvent.Raise(this);
    }
}

However as others already mentioned, you should be aware of the possible race condition in multi-threaded scenarios.

Gnafoo
Awesome! Thanks for posting this.
JBRWilkinson
That's neat, but you need to copy the event before raising it. (See Eric Lippert's comment a few posts up...)
Billy ONeal
I think Eric Lippert refers to another race condition that may still occur, even with the local copy. I found a blog post of him concerning that issue:http://blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspxThe problem is that thread-safe events require careful consideration and although the local copy solves one of the issues, it won't solve them all. Making it thread-safe will require additional code around the actual event.I didn't want to make the impression that the code is thread-safe, thus the last sentence.
Gnafoo