Why does C# require you to write a null check ever

2020-02-05 11:56发布

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.

标签: c# events null
9条回答
三岁会撩人
2楼-- · 2020-02-05 12:36

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.

查看更多
Summer. ? 凉城
3楼-- · 2020-02-05 12:39

Note that as of C# 6, the language now provides a concise syntax to perform this null check conveniently. E.g.:

public event EventHandler SomeEvent;

private void M()
{
    // raise the event:
    SomeEvent?.Invoke(this, EventArgs.Empty);
}

See Null Conditional Operator

查看更多
贪生不怕死
4楼-- · 2020-02-05 12:41

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
查看更多
Juvenile、少年°
5楼-- · 2020-02-05 12:47

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.

查看更多
ら.Afraid
6楼-- · 2020-02-05 12:48

Edit: As the OP points out, this answer does not address the body of the question. However, some may find it useful because it does provide an answer for the title of the question (when taken by itself):

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

It also provides context for the intent of the body of the question which some may find useful. So, for those reasons and this advice on Meta, I'll let this answer stand.


Original Text:

In its MSDN article How to: Publish Events that Conform to .NET Framework Guidelines (C# Programming Guide) ( Visual Studio 2013), Microsoft includes the following comment in its example:

// Make a temporary copy of the event to avoid possibility of 
// a race condition if the last subscriber unsubscribes 
// immediately after the null check and before the event is raised.

Here is a larger excerpt from Microsoft's example code that gives context to that comment.

// Wrap event invocations inside a protected virtual method 
// to allow derived classes to override the event invocation behavior 
protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
{
    // Make a temporary copy of the event to avoid possibility of 
    // a race condition if the last subscriber unsubscribes 
    // immediately after the null check and before the event is raised.
    EventHandler<CustomEventArgs> handler = RaiseCustomEvent;

    // Event will be null if there are no subscribers 
    if (handler != null)
    {
        // Format the string to send inside the CustomEventArgs parameter
        e.Message += String.Format(" at {0}", DateTime.Now.ToString());

        // Use the () operator to raise the event.
        handler(this, e);
    }
}
查看更多
贪生不怕死
7楼-- · 2020-02-05 12:50

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

查看更多
登录 后发表回答