There's the following pattern which is used to avoid a race condition when raising events in case another thread unsubscribes from MyEvent, making it null.
class MyClass
{
public event EventHandler MyEvent;
public void F()
{
EventHandler handler = MyEvent;
if(handler != null)
handler(this, EventArgs.Empty);
}
}
as opposed to the wrong way of doing it which is prone to this race condition:
class MyClass
{
public event EventHandler MyEvent;
public void F()
{
if(MyEvent != null)
MyEvent(this, EventArgs.Empty);
}
}
My question is, given that System.Delegate
is a reference type: in case MyEvent is not null, how come
EventHandler handler = MyEvent;
seems to copy its invocation list instead of obtaining the reference.
I would expect that having the MyEvent delegate assigned to the 'handler' variable, then once somebody changed MyEvent that the object that 'handler' references would be changed as well.
Obviously, that is not the case, otherwise this nifty little pattern wouldn't work.
I've looked into the .NET source code and still could not find my answer there (it's probably there, but I've looked for about an hour and couldn't find it, so here I am.) I've also read what the C# Language Specification has to say about events and delegates, but it doesn't address this matter.
Thanks for your time.
The EventHandler delegate is a predefined delegate that specifically represents an event handler method for an event that does not generate data. If your event does generate data, you must use the generic EventHandler<TEventArgs> delegate class. To associate the event with the method that will handle the event,...
Handling of the events is done by the built-in function known as EventHandler. Delegate is an important part of the eventhandler and when created, it aims towards the method eventhandler. Now that we have understood what the eventhandler is, let us move on to learn more about it.
Race condition is the scenario in programming where many threads compete to execute on the same code part resulting in undesirable results. Please have a look at the below code
To associate the event with the method that will handle the event, add an instance of the delegate to the event. The event handler is called whenever the event occurs, unless you remove the delegate. For more information about event handler delegates, see Handling and Raising Events.
Here are some diagrams that should hopefully clear up confusion about copying references and assignment.
In the above diagram, the reference contained in y
is copied into x
. No one's saying the object is copied; mind you—they point to the same object.
Forget about the +=
operator for a moment; what I want to highlight above is that y
is being assigned a different reference, to a new object. This does not affect x
because x
is its own variable. Remember, only the reference (the "address" in the diagram) had been copied to y
.
x
.The above diagrams depict string
objects, only because those are easy to represent graphically. But it's the same thing with delegates (and remember, standard events are just wrappers around delegate fields). You can see how by copying the reference in y
into x
above, we have created a variable which will not be affected by subsequent assignments to y
.
That is the whole idea behind the standard EventHandler
race condition "fix" we're all familiar with.
You are probably confused by this tricky little syntax:
someObject.SomeEvent += SomeEventHandler;
What's important to realize is that, as Ani points out in his answer, delegates are immutable reference types (think: just like string
). Many developers mistakenly think they are mutable because the above code looks like I am "adding" a handler to some mutable list. This isn't so; the +=
operator is an assignment operator: it takes the return value of the +
operator and assigns it to the variable on the left side.
(Think: int
is immutable, and yet I can do int x = 0; x += 1;
right? It's the same thing.)
EDIT: OK, technically this is not quite right. Here is what really happens. An event
is actually a wrapper around a delegate field that is accessible only (to external code) by the +=
and -=
operators, which are compiled to calls to add
and remove
, respectively. In this way it is very much like a property, which is (typically) a wrapper around a field, where accessing the property and calling =
are compiled to calls to get
and set
.
But the point still remains: when you write +=
, the add
method that gets called is internally assigning a reference to a new object to the internal delegate field. I apologize for oversimplifying this explanation in my initial answer; but the key principle to understand is the same.
By the way, I am not covering custom events where you can put your own logic inside the add
and remove
methods. This answer only applies to the "normal" case.
In other words, when you do this...
EventHandler handler = SomeEvent;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
...you are indeed copying a reference into a variable. Now that reference is in the local variable and won't itself be modified. If it was pointing to an actual object at the time of assignment, then it will continue to point at that same (immutable) object on the next line. If it was not pointing to an object (null
), then it will still not point to an object on the next line.
So if code elsewhere subscribed or unsubscribed to the event using +=
, what it really did was change the original reference to point to a completely new object. The old delegate object is still around, and you've got a reference to it: in your local variable.
I would expect that once I got the MyEvent delegate inside the 'handler' reference, once somebody would change MyEvent that the object that 'handler' references will be changed as well. [..] Notice that System.Delegate is a class and not a struct.
Although you are correct that delegate-types are references-types, they are immutable reference-types. From System.Delegate
:
"Delegates are immutable; once created, the invocation list of a delegate does not change.[...] Combining operations, such as Combine and Remove, do not alter existing delegates. Instead, such an operation returns a new delegate that contains the results of the operation, an unchanged delegate, or Nothing.
On another note, the only issue this pattern addresses is preventing the attempted invocation of a null delegate-reference. Events are prone to races despite this "fix".
I would like to point out that comparing this incident to the 'int' case is probably inherently wrong since even though 'int' is atomic, it is a value type.
But I think we've solved the case:
Combining operations, such as Combine and Remove, do not alter existing delegates. Instead, such an operation returns a new delegate that contains the results of the operation, an unchanged delegate, or null. A combining operation returns null when the result of the operation is a delegate that does not reference at least one method. A combining operation returns an unchanged delegate when the requested operation has no effect.
Delegate.CombineImpl Method shows the implementation.
I looked over the implementation of Delegate and MulticastDelegate in the .NET 4 source code. Neither of them declare the += or -= operator. Coming to think of it, in Visual Basic.NET you don't even have them, you use AddHandler, etc...
This means that the C# compiler implements this functionality and that the type doesn't really have anything to do with defining specialized operators.
So this leads me to a logical conclusion which is, when you do:
EventHandler handler = MyEvent;
the C# compiler translates it to
EventHandler handler = EventHandler.Combine(MyEvent)
I'm surprised how quickly this question was solved thanks to your help.
Thank you very much indeed!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With