I have moved!

I've moved my blog

Sunday, 11 May 2008

Delegates are Immutable

Suppose you see this in some code:

button.Click += new EventHandler(button_Click);

Naturally you would conclude that Click is an event and button_Click is a method name (formally described as a method group, because the name by itself identifies one or more methods with different parameters). After the above line of code has been executed, the method button_Click will run each time the Click event fires (presumably when the button is clicked).

Also these days we don't need to be so verbose and can just write:

button.Click += button_Click;

Perfectly straightforward. But full of assumptions that may not be true. I deliberately chose the name button_Click to make you think it identified a method, because that's the kind of name the Visual Studio IDE gives to automatically generated event handler methods. What if only the identifier was different?

button.Click += ButtonClicked; // Exhibit A

Maybe ButtonClicked is in fact not a method but another event. In which case, what does Exhibit A actually do?

Another way of phrasing all this: how does it differ from this next line of code?

button.Click += (s, e) => ButtonClicked(s, e); // Exhibit B

If ButtonClicked is a method name, then there is no effective difference between Exhibit A and Exhibit B. Exhibit B wastes a little bit of garbage collected memory, but otherwise it sets up something with identical behaviour. Exhibit A creates a delegate that calls the method ButtonClicked directly, whereas Exhibit B builds an anonymous method that calls ButtonClicked and then wraps it in a delegate. The result is an unnecessary layer of wrapping. So if someone were maintaining the code and they came across Exhibit B, they could safely change it to Exhibit A.

But what if, as seems more likely, ButtonClicked is an event? It all depends on what state ButtonClicked is in at the time. Suppose that before Exhibit A executes, there are currently no handlers attached to either event. The debugger would tell us that both button.Click and ButtonClicked have the special value null.

Immediately after Exhibit A executes, the debugger would continue to tell us exactly the same thing: Exhibit A would have no effect on anything.

But what if ButtonClicked had previously been set up as follows?

ButtonClicked += (s, e) => Console.WriteLine(e.ToString());

Then Exhibit A would have an effect. It would mean that the anonymous function attached to ButtonClicked would also execute if the button is clicked.

And what if a second handler were attached to ButtonClicked after Exhibit A was run?

ButtonClicked += (s, e) => Console.WriteLine("And again");

This change would have no effect at all on the behaviour when the button is clicked.

The reason for this is that delegates are immutable - they cannot be modified after they are constructed - and so should be treated like value types. Exhibit A could be interpreted as an instruction to make button.Click point to the exact same delegate object as ButtonClicked. And in fact, C# and the runtime are probably at liberty to do it that way. But as there is no way to modify an existing delegate, therefore it makes no difference whether the two events point to one delegate or to two separate copies.

When we attach our second handler to ButtonClicked, this results in the creation of a completely new delegate object that "multi-casts" invocations on to both handlers. However, the previous delegate object is still attached to button.Click.

Why is this important? Because the sort of place where you might want to set up this "chaining" of events is in the constructor of a window that owns a button. The desired behaviour is that when the button is clicked, the window's ButtonClicked event should fire. But if you try to achieve this with Exhibit A, you will find that it does not work, because in the window's constructor, nothing has been attached to ButtonClicked yet. What you actually need is Exhibit B.

And yet if ButtonClicked was a method, there would be no noticeable difference between the two.

So the short answer to the question, "What's the difference between Exhibit A and Exhibit B?" is...

It depends.

No comments: