I was just reading a page on events on MSDN, and I came across a snippet of example code that is puzzling me.
The code in question is this:
// 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;
I understand the intentions of the code, but I fail to see how that particular line is making a copy of anything. All it is doing is copying the reference; it's not actually making a deep copy of the delegate instance. So to that end, it doesn't actually prevent the race condition at all.
Am I missing something obvious here?
Delegates are immutable, so the reference obtained in that code is guaranteed to not change. If a user subscribes or unsubscribes after the null check, a new delegate will be created and set to the event. However, since you have a reference to a completely different object and invoke that, you don't have to worry about it being null.
You are correct; it is copying the reference.
However, delegates are immutable; when you add a handler to an event, a new delegate is created, combining the current handler(s) with the new one, and is then assigned to the field.
The Delegate instance that the field is referencing cannot change, so it does avoid the race condition.
Eric Lippert already covered this in a very detailed post.
This is from MSDN too..
"The invocation list of a delegate is an ordered set of delegates in which each element of the list invokes exactly one of the methods represented by the delegate. An invocation list can contain duplicate methods. During an invocation, methods are invoked in the order in which they appear in the invocation list. A delegate attempts to invoke every method in its invocation list; duplicates are invoked once for each time they appear in the invocation list. Delegates are immutable; once created, the invocation list of a delegate does not change."
if (whatever != null) whatever();
looks like it ensures that whatever
is never null when whatever()
is called, but it doesn't actually ensure that in a threaded scenario. A different thread can set whatever = null
between the check and the call.
Foo temp = whatever;
if (temp != null) temp();
This code removes the possibility of the null dereference, since temp
is a local and will therefore never be modified by a different thread. So it does prevent a race condition. It doesn't prevent all the relevant race conditions though. Eric Lippert did a more elaborate discussion of some other problems with the code.