Reputation: 697
Ive been coding for a while now, and just noticed something interesting. So normally when I'm implementing events in a class, I want to check the event's null status before I try to call it, so I'd use the following syntax:
private void OnEvent(EventArgs e)
{
if (Event != null) Event(this, e);
}
But I noticed something today in Visual Studio, it it's code simplification suggestions, it suggested the following syntax as a simplification:
private void OnEvent(EventArgs e)
{
Event?.Invoke(this, e);
}
Is anyone familiar with this "?" syntax? Is it a generic shorthand for checking null status of anything or just delegates? Its not part of Linq framework, its a built in syntax. Any insight into this and what's its used for would be helpful. I've dont quite a bit of searching but can't find anything on it specifically.
Upvotes: 0
Views: 279
Reputation: 660533
I left some comments above, but I feel that they are important enough to be called out into an answer.
First, as others have noted, this is the null conditional operator.
This version of the code is considered poor style because the event could become null after the check if modified on another thread:
private void OnEvent(EventArgs e)
{
if (Event != null) Event(this, e);
}
However, I note that multithreaded programs which invoke, subscribe and unsubscribe events on arbitrary threads are relatively rare.
The standard advice before the null conditional operator was added was to write the clumsy:
private void OnEvent(EventArgs e)
{
var ev = Event;
if (ev != null) ev(this, e);
}
This eliminates the null dereference if there is a race.
The null conditional operator is the same:
private void OnEvent(EventArgs e)
{
Event?.Invoke(this, e);
}
This is just a shorthand for var ev = Event; if (ev != null) ev.Invoke(this, e);
.
Now, people will tell you that the latter two versions, either with the local, or more concisely, with the ?.
, are "thread safe". They are not. These will not dereference null, but that is not the interesting race. The interesting race is:
Thread Alpha: create disposable object X -- say, a log file writer
Thread Alpha: subscribe X.H to event
Thread Bravo: Cause event to fire
Thread Bravo: Save X.H into local and check for null. It's not null.
Thread Alpha: Unsubscribe X.H from event
Thread Alpha: Call Dispose on X -- the log file is now closed.
Thread Bravo: Invoke X.H via the local
And now we have invoked a method on a disposed object, which, if the object is implemented correctly, should throw an "object disposed" exception. And if not, hey, it tries to write to a closed file and everything breaks, yay!
In a multithreaded environment, event handlers are required to be safe to call forever, even after they have been unsubscribed from the event. No amount of "thread safety" on the call site is going to solve that problem; this is a requirement of the handler. But how does the author of the handler know that they are going to be used in a multi-threaded environment, and plan accordingly? Often, they don't. So things break.
If you don't like that then don't write programs that use events on multiple threads. It is really hard to get right, and everyone -- sources and handlers both -- has to cooperate to make sure it works.
Now, some might say, isn't the solution to put a lock on the handler?
private object HandleLocker = new object();
...
private void OnEvent(EventArgs e)
{
lock (HandleLocker)
{
if (Event != null)
Event(this, e);
}
}
And then similarly add locks to the add
and remove
of Event
.
This is a cure that is worse than the disease.
Thread Bravo takes out a lock on some object Foo and obtains it.
Thread Charlie wishes to fire the event.
Thread Charlie takes out a lock on HandleLocker and obtains it.
Thread Charlie calls the handler. The handler blocks trying to obtain Foo.
Thread Bravo attempts to subscribe a new handler, and blocks on HandleLocker.
Now we have two threads each of which is waiting for the other to complete. Bravo cannot move until HandleLocker is released by Charlie, and Charlie cannot move until Foo is released by Bravo.
Never do this. Again: you are required to ensure that handlers may be invoked while stale if you are in a multithreaded event handling program, and you may not use locks to do it. That's a bad situation to be in; locks are the standard mechanism for managing threads.
Multithreading is hard. Don't write multithreaded programs if you don't understand all the ways that things can go wrong, and how to avoid them.
Upvotes: 6
Reputation: 38
This is called the Null-Conditional operator and is useful in a bunch of situations. It and the Null-Coalescing operator (??) are really clean, expressive ways of checking for null before dereferencing something or providing clean defaults.
For more info https://msdn.microsoft.com/en-CA/library/dn986595.aspx
Upvotes: 0
Reputation: 141
The ?
/ 'null conditional operator' is simply a shorthand way of testing for null before performing a member access or index operation.
You can read more here: https://msdn.microsoft.com/en-us/library/dn986595.aspx
Upvotes: 0