Delegates contain all that is needed to allow a method, with a specific signature and return type, to be invoked by your code. A delegate can be used similarly to an object; for example, it can be passed to methods and stored in a data structure. A delegate is used when, at design time, you do not know which method you need to call and the information to determine this is available only at runtime.
Another scenario is when the code calling a method is being developed
independently of the code that will supply the method to be called.
The classic example is a Windows Forms control. If you design a
control, you are unlikely to know what method should be called when
the application raises an event, so you must use a delegate. However,
when others use your control, they will typically decide at design
time which method to call. (For example, it’s common
to connect a Button
’s click
handler to a delegate at design time.)
This chapter’s recipes make use of delegates and events. These recipes cover handling each method invoked in a multicast delegate individually, synchronous delegate invocation versus asynchronous delegate invocation, and enhancing an existing class with events, among other topics. If you are not familiar with delegates and events, you should read the MSDN documentation on these topics. There are also good tutorials and example code showing you how to set up and use delegates and events.
You have added multiple delegates to create a multicast delegate. When this multicast delegate is fired, each delegate within it is fired in turn. You need to exert more control over such things as the order in which each delegate is fired, firing only a subset of delegates, or firing each delegate based on the success or failure of previous delegates.
Use the
GetInvocationList
method to obtain an array of
Delegate
objects. Next, iterate over this array
using a for
loop. You can then invoke each
Delegate
object in the array individually and
optionally retrieve its return value.
The following method creates a multicast delegate called
All
and then uses
GetInvocationList
to allow each delegate to be
fired individually, in reverse
order:
public void InvokeInReverse( ) { MultiInvoke MI1 = new MultiInvoke(TestInvoke.Method1); MultiInvoke MI2 = new MultiInvoke(TestInvoke.Method2); MultiInvoke MI3 = new MultiInvoke(TestInvoke.Method3); MultiInvoke All = MI1 + MI2 + MI3; Console.WriteLine("Fire delegates in reverse"); Delegate[] Delegates = All.GetInvocationList( ); for (int counter = Delegates.Length - 1; counter >= 0; counter--) { ((MultiInvoke)Delegates[counter])( ); } }
The following method fires every other delegate, starting with the first delegate in the list:
public void InvokeEveryOther( ) { MultiInvoke MI1 = new MultiInvoke(TestInvoke.Method1); MultiInvoke MI2 = new MultiInvoke(TestInvoke.Method2); MultiInvoke MI3 = new MultiInvoke(TestInvoke.Method3); MultiInvoke All = MI1 + MI2 + MI3; Delegate[] Delegates = All.GetInvocationList( ); Console.WriteLine("Fire every other delegate"); for (int counter = 0; counter < Delegates.Length; counter += 2) { ((MultiInvoke)Delegates[counter])( ); } }
In .NET, all delegates are implicitly multicast—that is, any
delegate can invoke multiple methods each time it is itself invoked.
In this recipe, we use the term
“multicast” to describe a delegate
that has been set up to invoke multiple methods. The following
delegate defines the MultiInvoke
delegate:
public delegate int MultiInvoke( );
The following class contains each of the methods that will be called
by the MultiInvoke
multicast delegate:
public class TestInvoke { public static int Method1( ) { Console.WriteLine("Invoked Method1"); return (1); } public static int Method2( ) { Console.WriteLine("Invoked Method2"); return (2); } public static int Method3( ) { Console.WriteLine("Invoked Method3"); return (3); } }
It is also possible to decide whether to continue firing delegates in
the list based on the return value of the currently firing delegate.
The following method fires each delegate, stopping only when a
delegate returns a false
value:
public void InvokeWithTest( ) { MultiInvokeTF MI1 = new MultiInvokeTF(TestInvokeTF.Method1); MultiInvokeTF MI2 = new MultiInvokeTF(TestInvokeTF.Method2); MultiInvokeTF MI3 = new MultiInvokeTF(TestInvokeTF.Method3); MultiInvokeTF All = MI1 + MI2 + MI3; bool retVal = true; Console.WriteLine( "Invoke individually (Call based on previous return value):"); foreach (MultiInvokeTF individualMI in All.GetInvocationList( )) { if (retVal) { retVal = individualMI( ); } else { // This break is not required; it is an optimization to // prevent the loop from continuing to execute. break; } } }
The following delegate defines the MultiInvokeTF
delegate:
public delegate bool MultiInvokeTF( );
The following class contains each of the methods that will be called
by the MultiInvokeTF
multicast delegate:
public class TestInvokeTF { public static bool Method1( ) { Console.WriteLine("Invoked Method1"); return (true); } public static bool Method2( ) { Console.WriteLine("Invoked Method2"); return (false); } public static bool Method3( ) { Console.WriteLine("Invoked Method3"); return (true); } }
A delegate, when called, will invoke all delegates stored within its invocation list. These delegates are invoked sequentially from the first to the last one added. Once the multicast delegate is called, you cannot change when—or if—any delegate in the list is called.
Fortunately,
with the use of the GetInvocationList
method of
the MulticastDelegate
class, you can obtain each
delegate in the invocation list of a multicast delegate. This method
accepts no parameters and returns an array of
Delegate
objects that corresponds to the
invocation list of the delegate on which this method was called. The
returned Delegate
array contains the delegates of
the invocation list in the order in which they would normally be
called; that is, the zeroth element in the
Delegate
array contains the
Delegate
object that is normally called first.
This application of the GetInvocationList
method
gives us the ability to control exactly when and how the delegates in
a multicast delegate are invoked and allows us to prevent the
continued invocation of delegates when one delegate fails. This
ability is important if each delegate is manipulating data and one of
the delegates fails in its duties but does not throw an exception. If
one delegate fails in its duties and the remaining delegates rely on
all previous delegates to succeed, you must quit invoking delegates
at the point of failure. Note that an exception will force the
invocation of delegates to cease, but throwing an exception is an
expensive process. This recipe handles a delegate failure more
efficiently, and also provides more flexibility in dealing with these
errors. For example, you can write logic to specify which delegates
are to be invoked, based on the performance of previously invoked
delegates.
See Recipe 7.2 and Recipe 7.3; see the “Delegate Class” and “Delegate.GetInvocationList Method” topics in the MSDN documentation.