Tuesday, April 16, 2024 05:00

Table of contents >> Delegates, Lambda Expressions, Events > Delegate chaining

Delegate chaining

An useful property of delegate objects is that multiple objects can be assigned to one delegate instance using the + operator, process called delegate chaining.

Delegate chaining isn’t really useful until we will get to events and event subscribers, which will come in a future lesson, but it’s better to describe the behavior now, after you’ve seen a bit of delegates inner workings. So, let’s create a delegate, as we did in the previous lessons:

I declared a delegate, SomeDelegate, and three methods that can be assigned to it, because they have the same signature: Foo(), Goo() and Sue(). Then, I created an instance of SomeDelegate, named _delegate, and I assigned Foo to it. Then, I invoked _delegate, I assigned Goo to it, and invoked it again, with this result:

Delegate assignment and invocation Nothing new so far, but the way of doing it, assigning, and invoking, and then re-assigning and invoking again, it’s not very elegant. Besides, what if we want to call two methods at the same time when we invoke the delegate? As soon as we re-assign a new method to it, the original assignment gets lost.

The solution for these problems is delegate chaining. We can assign multiple methods to the same delegate, without overriding previous assigned methods, effectively creating a chain of methods assigned to it, which all get executed when we invoke the delegate:

It is really easy. I just used the += operator to add more methods to the existing assigned methods of my delegate. Of course, as explained in the lesson operators, x += y is just a shortened form of writing x = x + y. In other words, _delegate = _delegate + SomeMethod.  And this is the result:

chained delegate invocationSo, the important thing to get from this is that a delegate not necessarily references a single method. We can add multiple methods, we can remove from the added methods, and we can even add a method multiple times:

Can you figure out which of the Foo methods will be removed when the _delegate -= Foo instruction gets executed? As you notice, Foo is assigned to the delegate twice, so, which will get removed, first, last or both?

If you execute the code, you will get this image:

so, the last assigned method with that name gets removed.

Let’s analyze further what happens behind the scenes when the compiler sees a delegate combine operation. We have this expression:

which actually means this:

In this case, when the compiler encounters the + operator, it doesn’t actually perform addition between delegates, it combines them, which means that it references a static method called Delegate.Combine(), which takes a params Delegate array as argument, or, in our case, it takes _delegate as first parameter (the delegate to combine with) and Goo (the method to be combined with) as the second:

At this point, you will get an error in Visual Studio, because the compiler doesn’t really know what to do with Goo: the compile time type is Delegate (because Delegate is the base class of all the delegate types, in an inheritance chain that follows this hierarchy: Delegate -> MulticastDelegate -> SomeDelegate), and we are giving it a method. We have to give it a delegate, so, it gets converted to this:

In other words, it takes a new SomeDelegate and combines it with _delegate, which is also of type SomeDelegate. Again, you will still get an error, and this is because Delegate.Combine() returns a Delegate, and we are trying to assign it to a SomeDelegate variable. Hence, we need to do a casting:

As a conclusion, the compiler does quite a lot of code changes in order for us to just use += operator, instead of writing all that instruction.

You might ask yourself if you could directly assign more methods to a delegate, in a single assignment, like we can with primitive types. For instance:

In this case, you would actually get an error in Visual Studio, because the compiler thinks you want to add two methods together, which is impossible, not create a delegate chain assignment. If you want it to understand that we want a delegate chain, we can wrap the first method in a delegate:

In this case the compiler sees that we declare a new SomeDelegate, initialized with Foo, and it is this delegate that we are adding with Goo, which is allowed. The addition would also have a result of type SomeDelegate, which, when added to Sue, repeats the process. Finally, we can also cast the first operand:

Finally, what would happen if one of the chained methods would throw an exception? How would the program cope with the remaining chained methods? Would they get executed? Lets see:

In this case, I’ve modified Goo to throw an exception when executed. Let’s see the result when running the program:

Delegate chaining exception handlingSo, whenever a method in the methods chain throws an exception, all the remaining methods will not get executed. In order to prevent this, I need to modify my program a bit, so I can handle exceptions with a Try…Catch block:

And now everything works fine and I am also able to see the exception messages, because I asked for the invocation list of _delegate, and then I invoked each of those methods inside a Try…Catch block:

Delegate chains are immutable, just like strings, which means that you can create new ones as much as you want, but you cannot modify existing ones. This actually means that when you create a delegate and assign a method to it, the compiler creates an object in the Stack memory, which points to the address of that method, located on the Heap memory. If you add another method to the delegate, the compiler actually creates a new object on the Stack which points to the original method on the Heap, but it also points to a second delegate object, which in turn, points to the added method, while the original delegate object on the stack gets deleted by the Garbage Collector. So, the process of delegate chaining is quite complex under the hood.

Delegate chaining is usually used for a programming principle called the observer pattern, of which we will learn in the future.

Tags: , , ,

2 Responses to “Delegate chaining”

  1. Brodacz says:

    Thank you so much for this super clear explanation!

Leave a Reply

Follow the white rabbit