Jul 27

Getting started with delegates – part III

Posted in .NET Basics C#      Comments Off on Getting started with delegates – part III

I’ve ended the previous post by saying what we can chain several method calls and that is what we’ll see in this post. With chaining, we can combine several delegates so that calling one of them ends up invoking all of them. A quick example is the best way to see this feature in action (I’m reusing the Logger delegate introduced in the previous posts):

Logger del1 = ( info ) => Console.WriteLine( "del1" );
Logger del2 = ( info ) => Console.WriteLine( "del2" );
Logger del3 = ( info ) => Console.WriteLine( "del3" );
var chain = (Logger)Delegate.Combine( null, del1 );
chain = ( Logger ) Delegate.Combine( chain, del2 );
chain = ( Logger ) Delegate.Combine( chain, del3 );
chain( "hi" );//all delegates are called

I’ve started by declaring three Logger variables which are initialized with three different lambda expressions. After that, I rely on the static Combine method for creating new delegates which “combine” our delegates variables. As you can see, I start by combining del1 with null. When this happens, chain an del1 end up referencing the same memory address. The second Combine call produces more interesting results. Since chain already references a delegate, then this Combine call ends up creating a new delegate whose internal delegate list is initialized with an array that holds the two elements passed to the Combine method.

As you’re probably expecting, the last Combine call will also return a new Delegate but notice that its internal invocation list will have three (not two!) delegate references. You might expect it to have two since our chain variable holds a delegate returned from the second Combine call and you might think that the third call would combine that delegate with del3. However, that doesn’t happen and the last delegate’s internal delegate list will reference our 3 delXXX delegates.  One interesting result of this behavior is that the delegate returned by the second Combine call can be garbage collected since there isn’t any variable referencing it. Btw, you can access this internal delegate list through the GetInvocationList method.

Now, if we invoke chain, all delegates’ methods end up being called. As we’ve seen, calling a delegate through the method syntax is the same as executing the Invoke method. Internally, this method will check the delegate list. When that list is not empty, it will call all delegates present on that list (when the list is empty, it will use the target and method fields for invoking the correct method). You’re probably wondering what happens when our delegate matches a method that returns a value (different from void). Well, in that case, the returned result is the one that is returned by the last delegate that gets executed.

By now, you’re probably thinking that if we can add delegates to the internal list, then we can probably remove them from that list. And that’s true: we can remove a delegate from the list by using the static Remove method. This method expects two parameters: the first references the delegate whose internal list will be searched for; the second identifies the delegate that should be removed from the list ( match is found when a delegate’s target and method fields reference the same objects as the delegate that is passed through the second parameter).

When there are several delegates in the array, the method  will return a new delegate whose internal delegate list has all the remaining delegates. When there’s only one delegate and that delegate is being removed, the method will return a reference to that delegate. It’s also important to understand that Remove will only remove one delegate from the list (even if there are several delegates that match the delegate passed to Remove through the second list). Now that we understand the theory, here’s some code that illustrates the use of the Remove method:

chain = ( Logger ) Delegate.Remove( chain, del2 );
chain( "h" ); //only del1 and del3 get executed

And that’s it for now. Stay tuned for more!