When a head of state dies, the president of the United States typically does not have time to attend the funeral personally. Instead, he dispatches a delegate. Often this delegate is the vice president, but sometimes the VP is unavailable and the president must send someone else, such as the secretary of state or even the first lady. He does not want to “hardwire” his delegated authority to a single person; he might delegate this responsibility to anyone who is able to execute the correct international protocol.
The president defines in advance what authority will be delegated (attend the funeral), what parameters will be passed (condolences, kind words), and what value he hopes to get back (good will). He then assigns a particular person to that delegated responsibility at “runtime” as the course of his presidency progresses.
In programming, you are often faced with situations where you need to
execute a particular action, but you don’t know in advance
which method, or even which object, you’ll want to call upon to
execute that action. For example, a button might know that it must
notify some object when it is pushed, but it
might not know which object or objects need to be notified. Rather
than wiring the button to a particular object, you will connect the
button to a delegate
and then resolve that delegate to a particular method when the
program executes.
In the early, dark, and primitive days of computing, a program would begin execution and then proceed through its steps until it completed. If the user was involved, the interaction was strictly controlled and limited to filling in fields.
Today’s Graphical User Interface (GUI) programming model requires a different approach, known as event-driven programming . A modern program presents the user interface and waits for the user to take an action. The user might take many different actions, such as choosing among menu selections, pushing buttons, updating text fields, clicking icons, and so forth. Each action causes an event to be raised. Other events can be raised without direct user action, such as events that correspond to timer ticks of the internal clock, email being received, file-copy operations completing, etc.
An event is the encapsulation of the idea that “something happened” to which the program must respond. Events and delegates are tightly coupled concepts because flexible event handling requires that the response to the event be dispatched to the appropriate event handler. An event handler is typically implemented in C# as a delegate.
Delegates are also used as callbacks so that one class can say to another “do this work and when you’re done, let me know.” This second usage will be covered in detail in Chapter 19. Delegates can also be used to specify methods that will only become known at runtime, a topic that will be developed in the following sections.
In C#, delegates are first-class objects, fully supported by the language. Technically, a delegate is a reference type used to encapsulate a method with a specific signature and return type. You can encapsulate any matching method in that delegate. (In C++ and many other languages, you can accomplish this requirement with function pointers and pointers to member functions. Unlike function pointers, delegates are object-oriented and type-safe.)
A delegate is created with the delegate
keyword,
followed by a return type and the signature of the methods that can
be delegated to it, as in the following:
public delegate int WhichIsFirst(object obj1, object obj2);
This declaration defines a delegate named
WhichIsFirst
which will encapsulate any method
that takes two object
s as parameters and that
returns an int
.
Once the delegate is defined, you can encapsulate a member method with that delegate by instantiating the delegate, passing in a method that matches the return type and signature.
Delegates are used to specify the kinds of methods that can be used to handle events and to implement callbacks in your applications. They can also be used to specify static and instance methods that won’t be known until runtime.
Suppose, for example, that you want to create a simple container
class called a Pair
that can hold and sort any two
objects passed to it. You can’t know in advance what kind of
objects a Pair
will hold, but by creating methods
within those objects to which the sorting task can be delegated, you
can delegate responsibility for determining their order to the
objects themselves.
Different objects will sort differently; for
example, a Pair
of counter
objects might sort in numeric order, while a Pair
of Buttons
might sort alphabetically by their
name. As the author of the Pair
class, you want
the objects in the pair to have the responsibility of knowing which
should be first and which should be second. To accomplish this, you
will insist that the objects to be stored in the
Pair
must provide a method that tells you how to
sort the objects.
You define the method you require by creating a delegate that defines
the signature and return type of the method the object (e.g.,
Button
) must provide to allow the
Pair
to determine which object should be first and
which should be second.
The Pair
class defines a delegate,
WhichIsFirst
. The
Sort
method will take a parameter, an instance of
WhichIsFirst
. When the Pair
needs to know how to order its objects it will invoke the delegate
passing in its two objects as parameters. The responsibility for
deciding which of the two objects comes first is delegated to the
method encapsulated by the delegate.
To test the delegate, you will create two classes, a
Dog
class and a Student
class.
Dogs
and Students
have little
in common, except that they both implement methods that can be
encapsulated by WhichComesFirst
, and thus both
Dog
objects and Student
objects
are eligible to be held within Pair
objects.
In the test program you will create a couple of
Student
s and a couple of Dog
s,
and store them each in a Pair
. You will then
create delegate objects to encapsulate their respective methods that
match the delegate signature and return type, and you’ll ask
the Pair
objects to sort the
Dog
and Student
objects.
Let’s take this step by step.
You begin by creating a Pair
constructor that
takes two objects and stashes them away in a private array:
public class Pair { // two objects, added in order received public Pair(object firstObject, object secondObject) { thePair[0] = firstObject; thePair [1] = secondObject; } // hold both objects private object[]thePair = new object[2];
Next, you override ToString( )
to
obtain the string value of the two objects:
public override string ToString( ) { return thePair [0].ToString() + ", " + thePair [1].ToString( ); }
You now have two objects in your Pair
and you can
print out their values. You’re ready to sort them and print the
results of the sort. You can’t know in advance what kind of
objects you will have, so you would like to delegate the
responsibility of deciding which object comes first in the sorted
Pair
to the objects themselves. Thus, you require
that each object stored in a Pair
implement a
method to return which of the two comes first. The method will take
two objects (of whatever type) and return an enumerated value:
theFirstComesFirst
if the first object comes
first, and theSecondComesFirst
if the second does.
These required methods will be encapsulated by the delegate
WhichIsFirst
that you define within the
Pair
class:
public delegate comparison WhichIsFirst(object obj1, object obj2);
The return value is of type comparison
, the
enumeration.
public enum comparison { theFirstComesFirst = 1, theSecondComesFirst = 2 }
Any static method that takes two objects and returns a
comparison
can be encapsulated by this delegate at
runtime.
You can now define the Sort
method for the
Pair
class:
public void Sort(WhichIsFirst theDelegatedFunc) { if (theDelegatedFunc(thePair[0],thePair[1]) == comparison.theSecondComesFirst) { object temp = thePair[0]; thePair[0] = thePair[1]; thePair[1] = temp; } }
This method takes a parameter: a delegate of type
WhichIsFirst
named
theDelegatedFunc
. The Sort( )
method delegates responsibility for deciding which of the two objects
in the Pair
comes first to the method encapsulated
by that delegate. In the body of the Sort( )
method it invokes the delegated method and examines the return value,
which will be one of the two enumerated values of
comparsion
.
If the value returned is theSecondComesFirst
, the
objects within the pair are swapped; otherwise no action is taken.
Notice that theDelegatedFunc
is the name of the
parameter to represent the method encapsulated by the delegate. You
can assign any method (with the appropriate return value and
signature) to this parameter. It is as if you had a method which took
an int
as a parameter:
int SomeMethod (int myParam){//...}
The parameter name is myParam
, but you can pass in
any int
value or variable. Similarly the parameter
name in the delegate example is theDelegatedFunc
,
but you can pass in any method that meets the return value and
signature defined by the delegate WhichIsFirst
.
Imagine you are sorting students by name. You write a method that
returns theFirstComesFirst
if the first
student’s name comes first and
theSecondComesFirst
if the second student’s
name does. If you pass in “Amy, Beth” the method will
return theFirstComesFirst
, and if you pass in
“Beth, Amy” it will return
theSecondComesFirst
. If you get back
theSecondComesFirst
, the Sort
method reverses the items in its array, setting Amy to the first
position and Beth to the second.
Now add one more method, ReverseSort
, which will
put the items into the array in reverse order:
public void ReverseSort(WhichIsFirst theDelegatedFunc) { if (theDelegatedFunc(thePair[0], thePair[1]) == comparison.theFirstComesFirst) { object temp = thePair[0]; thePair[0] = thePair[1]; thePair[1] = temp; } }
The logic here is identical to the Sort( )
, except
that this method performs the swap if the delegated method says that
the first item comes first. Because the delegated function thinks the
first item comes first, and this is a reverse sort, the result you
want is for the second item to come first. This time if you pass in
“Amy, Beth,” the delegated function returns
theFirstComesFirst
(i.e., Amy should come first),
but because this is a reverse sort it swaps the
values, setting Beth first. This allows you to use the same delegated
function as you used with Sort
, without forcing
the object to support a function that returns the reverse sorted
value.
Now all you need are some objects to sort. You’ll create two
absurdly simple classes: Student
and
Dog
. Assign Student
objects a
name at creation:
public class Student { public Student(string name) { this.name = name; }
The Student
class requires two methods, one to
override ToString( )
and the other to be
encapsulated as the delegated method.
Student
must override ToString( )
so
that the ToString( )
method in
Pair
, which invokes ToString( )
on the contained objects, will work properly: the implementation does
nothing more than return the student’s name (which is already a
string object):
public override string ToString( )
{
return name;
}
It must also implement a method to which Pair.Sort( )
can delegate the responsibility of determining which of
two objects comes first:
return (String.Compare(s1.name, s2.name) < 0 ? comparison.theFirstComesFirst : comparison.theSecondComesFirst);
String.Compare
is a .NET Framework method on the
String
class which compares two strings and
returns less than zero if the first is smaller and greater than zero
if the second is smaller, and returns zero if they are the same. This
method is discussed in some detail in Chapter 10.
Notice that the logic here returns
theFirstComesFirst
only if the first string is
smaller; if they are the same or the second is larger, this method
returns theSecondComesFirst
.
Notice that the WhichStudentComesFirst( )
method
takes two objects as parameters and returns a
comparison
. This qualifies it to be a
Pair.WhichIsFirst
delegated method, whose
signature and return value it matches.
The second class is Dog
. For our purposes,
Dog
objects will be sorted by weight, lighter dogs
before heavier. Here’s the complete declaration of
Dog
:
public class Dog { public Dog(int weight) { this.weight=weight; } // dogs are ordered by weight public static comparison WhichDogComesFirst( Object o1, Object o2) { Dog d1 = (Dog) o1; Dog d2 = (Dog) o2; return d1.weight > d2.weight ? theSecondComesFirst : theFirstComesFirst; } public override string ToString( ) { return weight.ToString( ); } private int weight; }
Notice that the Dog
class also overrides
ToString
and implements a static method with the
correct signature for the delegate. Notice also that the
Dog
and Student
delegate
methods do not have the same name. They do not need to have the same
name, as they will be assigned to the delegate dynamically at
runtime.
You can call your delegated method names anything you like, but
creating parallel names (e.g., WhichDogComesFirst
and WhichStudentComesFirst
) makes the code easier
to read, understand, and maintain.
Example 12-1 is the complete program, which illustrates how the delegate methods are invoked.
Example 12-1. Working with delegates
namespace Programming_CSharp
{
using System;
public enum comparison
{
theFirstComesFirst = 1,
theSecondComesFirst = 2
}
// a simple collection to hold 2 items
public class Pair
{
// the delegate declaration
public delegate comparison
WhichIsFirst(object obj1, object obj2);
// passed in constructor take two objects,
// added in order received
public Pair(
object firstObject,
object secondObject)
{
thePair[0] = firstObject;
thePair[1] = secondObject;
}
// public method which orders the two objects
// by whatever criteria the object likes!
public void Sort(
WhichIsFirst theDelegatedFunc)
{
if (theDelegatedFunc(thePair[0],thePair[1])
== comparison.theSecondComesFirst)
{
object temp = thePair[0];
thePair[0] = thePair[1];
thePair[1] = temp;
}
}
// public method which orders the two objects
// by the reverse of whatever criteria the object likes!
public void ReverseSort(
WhichIsFirst theDelegatedFunc)
{
if (theDelegatedFunc(thePair[0],thePair[1]) ==
comparison.theFirstComesFirst)
{
object temp = thePair[0];
thePair[0] = thePair[1];
thePair[1] = temp;
}
}
// ask the two objects to give their string value
public override string ToString( )
{
return thePair[0].ToString( ) + ", "
+ thePair[1].ToString( );
}
// private array to hold the two objects
private object[] thePair = new object[2];
}
public class Dog
{
public Dog(int weight)
{
this.weight=weight;
}
// dogs are ordered by weight
public static comparison WhichDogComesFirst(
Object o1, Object o2)
{
Dog d1 = (Dog) o1;
Dog d2 = (Dog) o2;
return d1.weight > d2.weight ?
comparison.theSecondComesFirst :
comparison.theFirstComesFirst;
}
public override string ToString( )
{
return weight.ToString( );
}
private int weight;
}
public class Student
{
public Student(string name)
{
this.name = name;
}
// students are ordered alphabetically
public static comparison
WhichStudentComesFirst(Object o1, Object o2)
{
Student s1 = (Student) o1;
Student s2 = (Student) o2;
return (String.Compare(s1.name, s2.name) < 0 ?
comparison.theFirstComesFirst :
comparison.theSecondComesFirst);
}
public override string ToString( )
{
return name;
}
private string name;
}
public class Test
{
public static void Main( )
{
// create two students and two dogs
// and add them to Pair objects
Student Jesse = new Student("Jesse");
Student Stacey = new Student ("Stacey");
Dog Milo = new Dog(65);
Dog Fred = new Dog(12);
Pair studentPair = new Pair(Jesse,Stacey);
Pair dogPair = new Pair(Milo, Fred);
Console.WriteLine("studentPair : {0}",
studentPair.ToString( ));
Console.WriteLine("dogPair : {0}",
dogPair.ToString( ));
// Instantiate the delegates
Pair.WhichIsFirst theStudentDelegate =
new Pair.WhichIsFirst(
Student.WhichStudentComesFirst);
Pair.WhichIsFirst theDogDelegate =
new Pair.WhichIsFirst(
Dog.WhichDogComesFirst);
// sort using the delegates
studentPair.Sort(theStudentDelegate);
Console.WriteLine("After Sort studentPair : {0}",
studentPair.ToString( ));
studentPair.ReverseSort(theStudentDelegate);
Console.WriteLine("After ReverseSort studentPair : {0}",
studentPair.ToString( ));
dogPair.Sort(theDogDelegate);
Console.WriteLine("After Sort dogPair : {0}",
dogPair.ToString( ));
dogPair.ReverseSort(theDogDelegate);
Console.WriteLine("After ReverseSort dogPair : {0}",
dogPair.ToString( ));
}
}
}
Output:
studentPair : Jesse, Stacey
dogPair : 65, 12
After Sort studentPair : Jesse, Stacey
After ReverseSort studentPair : Stacey, Jesse
After Sort dogPair : 12, 65
After ReverseSort dogPair : 65, 12
The Test
program creates two
Student
objects and two Dog
objects and then adds them to Pair
containers. The
student constructor takes a string for the student’s name and
the dog constructor takes an int
for the
dog’s weight.
Student Jesse = new Student("Jesse"); Student Stacey = new Student ("Stacey"); Dog Milo = new Dog(65); Dog Fred = new Dog(12); Pair studentPair = new Pair(Jesse,Stacey); Pair dogPair = new Pair(Milo, Fred); Console.WriteLine("studentPair : {0}", studentPair.ToString( )); Console.WriteLine("dogPair : {0}", dogPair.ToString( ));
It then prints the contents of the two Pair
containers to see the order of the objects. The output looks like
this:
studentPair : Jesse, Stacey dogPair : 65, 12
As expected, the objects are in the order in which they were added to
the Pair
containers. We next instantiate two
delegate objects:
Pair.WhichIsFirst theStudentDelegate = new Pair.WhichIsFirst( Student.WhichStudentComesFirst); Pair.WhichIsFirst theDogDelegate = new Pair.WhichIsFirst( Dog.WhichDogComesFirst);
The first delegate, theStudentDelegate
, is created
by passing in the appropriate static method from the
Student
class. The second delegate,
theDogDelegate
, is passed a static method from the
Dog
class.
The delegates are now objects that can be passed to methods. You pass
the delegates first to the Sort
method of the
Pair
object, and then to the
ReverseSort
method. The results are printed to the
console:
After Sort studentPair : Jesse, Stacey After ReverseSort studentPair : Stacey, Jesse After Sort dogPair : 12, 65 After ReverseSort dogPair : 65, 12
A
disadvantage
of Example 12-1 is that it forces the calling class,
in this case Test
, to instantiate the delegates it
needs in order to sort the objects in a Pair
. It
would be nice to get the delegate from the Student
or Dog
class. You can do this by giving each class
its own static delegate. Thus, you can modify
Student
to add this:
public static readonly Pair.WhichIsFirst OrderStudents
=
new Pair.WhichIsFirst(Student.WhichStudentComesFirst);
This creates a static,
readonly
delegate named
OrderStudents
.
Marking OrderStudents
readonly
denotes that once this static field is created, it will not be
modified.
You can create a similar delegate within Dog
:
public static readonly Pair.WhichIsFirst OrderDogs
=
new Pair.WhichIsFirst(Dog. WhichDogComesFirst);
These are now static fields of their respective classes. Each is prewired to the appropriate method within the class. You can invoke delegates without declaring a local delegate instance. You just pass in the static delegate of the class:
studentPair.Sort(theStudentDelegate); Console.WriteLine("After Sort studentPair : {0}", studentPair.ToString( ));studentPair.ReverseSort(Student.OrderStudents);
Console.WriteLine("After ReverseSort studentPair : {0}", studentPair.ToString( ));dogPair.Sort(Dog.OrderDogs);
Console.WriteLine("After Sort dogPair : {0}", dogPair.ToString( ));dogPair.ReverseSort(Dog.OrderDogs);
Console.WriteLine("After ReverseSort dogPair : {0}", dogPair.ToString( ));
The output from these changes is identical to the previous example.
The
problem with static delegates is that they
must be instantiated, whether or not they are ever used, as with
Student
and Dog
in the previous
example. You can improve these classes by changing the static
delegate fields to properties.
For Student
, you take out the declaration:
public static readonly Pair.WhichIsFirst OrderStudents = new Pair.WhichIsFirst(Student.WhichStudentComesFirst);
and replace it with:
public static Pair.WhichIsFirst OrderStudents { get { return new Pair.WhichIsFirst(WhichStudentComesFirst); } }
Similarly, you replace the Dog
static field with:
public static Pair.WhichIsFirst OrderDogs { get { return new Pair.WhichIsFirst(WhichDogComesFirst); } }
The assignment of the delegates is unchanged:
studentPair.Sort(Student.OrderStudents); dogPair.Sort(Dog.OrderDogs);
When the OrderStudent
property is accessed, the
delegate is created:
return new Pair.WhichIsFirst(WhichStudentComesFirst);
The key advantage is that the delegate is not created until it is
requested. This allows the test class to determine when it needs a
delegate but still allows the details of the creation of the delegate
to be the responsibility of the Student
(or
Dog
) class.
Delegates can help you create a system in which the user can dynamically decide on the order of operations. Suppose you have an image processing system in which an image can be manipulated in a number of well-defined ways, such as blurring, sharpening, rotating, filtering, and so forth. Assume, as well, that the order in which these effects are applied to the image is important. The user wishes to choose from a menu of effects, applying all that he likes, and then telling the image processor to run the effects, one after the other in the order that he has specified.
You can create delegates for each operation and add them to an ordered collection, such as an array, in the order you’d like them to execute. Once all the delegates are created and added to the collection, you simply iterate over the array, invoking each delegated method in turn.
You begin by creating a class Image
to represent
the image that will be processed by the
ImageProcessor
:
public class Image { public Image( ) { Console.WriteLine("An image created"); } }
You can imagine that this stands in for a .gif or .jpeg file or other image.
The ImageProcessor
then declares a delegate. You
can of course define your delegate to return any type and take any
parameters you like. For this example you’ll define the
delegate to encapsulate any method that returns
void
and takes no arguments:
public delegate void DoEffect( );
The ImageProcessor
then declares a number of
methods, each of which processes an image
and each
of which matches the return type and signature of the delegate:
public static void Blur( ) { Console.WriteLine("Blurring image"); } public static void Filter( ) { Console.WriteLine("Filtering image"); } public static void Sharpen( ) { Console.WriteLine("Sharpening image"); } public static void Rotate( ) { Console.WriteLine("Rotating image"); }
In a production environment these methods would be very complicated,
and they’d actually do the work of blurring, filtering,
sharpening, and rotating the image
.
The ImageProcessor
class needs an array to hold
the delegates that the user picks, a variable to hold the running
total of how many effects are in the array, and of course a variable
for the image
itself:
DoEffect[] arrayOfEffects; Image image; int numEffectsRegistered = 0;
The ImageProcessor also
needs a method to add
delegates to the array:
public void AddToEffects(DoEffect theEffect) { if (numEffectsRegistered >= 10) { throw new Exception("Too many members in array"); } arrayOfEffects[numEffectsRegistered++] = theEffect; }
It needs another method to actually call each method in turn:
public void ProcessImages( ) { for (int i = 0;i < numEffectsRegistered;i++) { arrayOfEffects[i]( ); } }
Finally, you need only declare the static delegates that the client can call, hooking them to the processing methods:
public DoEffect BlurEffect = new DoEffect(Blur); public DoEffect SharpenEffect = new DoEffect(Sharpen); public DoEffect FilterEffect = new DoEffect(Filter); public DoEffect RotateEffect = new DoEffect(Rotate);
In a production environment in which you might have dozens of effects, you might choose to make these properties rather than static methods. That would save creating the effects unless they are needed at the cost of making the program slightly more complicated.
The client code would normally have an interactive user-interface
component, but we’ll simulate that by choosing the effects,
adding them to the array, and then calling
ProcessImage
, as shown in Example 12-2.
Example 12-2. Using an array of delegates
namespace Programming_CSharp
{
using System;
// the image which we'll manipulate
public class Image
{
public Image( )
{
Console.WriteLine("An image created");
}
}
public class ImageProcessor
{
// declare the delegate
public delegate void DoEffect( );
// create various static delegates tied to member methods
public DoEffect BlurEffect =
new DoEffect(Blur);
public DoEffect SharpenEffect =
new DoEffect(Sharpen);
public DoEffect FilterEffect =
new DoEffect(Filter);
public DoEffect RotateEffect =
new DoEffect(Rotate);
// the constructor initializes the image and the array
public ImageProcessor(Image image)
{
this.image = image;
arrayOfEffects = new DoEffect[10];
}
// in a production environment we'd use a more
// flexible collection.
public void AddToEffects(DoEffect theEffect)
{
if (numEffectsRegistered >= 10)
{
throw new Exception(
"Too many members in array");
}
arrayOfEffects[numEffectsRegistered++]
= theEffect;
}
// the image processing methods...
public static void Blur( )
{
Console.WriteLine("Blurring image");
}
public static void Filter( )
{
Console.WriteLine("Filtering image");
}
public static void Sharpen( )
{
Console.WriteLine("Sharpening image");
}
public static void Rotate( )
{
Console.WriteLine("Rotating image");
}
public void ProcessImages( )
{
for (int i = 0;i < numEffectsRegistered;i++)
{
arrayOfEffects[i]( );
}
}
// private member variables...
private DoEffect[] arrayOfEffects;
private Image image;
private int numEffectsRegistered = 0;
}
// test driver
public class Test
{
public static void Main( )
{
Image theImage = new Image( );
// no ui to keep things simple, just pick the
// methods to invoke, add them in the required
// order, and then call on the image processor to
// run them in the order added.
ImageProcessor theProc =
new ImageProcessor(theImage);
theProc.AddToEffects(theProc.BlurEffect);
theProc.AddToEffects(theProc.FilterEffect);
theProc.AddToEffects(theProc.RotateEffect);
theProc.AddToEffects(theProc.SharpenEffect);
theProc.ProcessImages( );
}
}
}
Output:
An image created
Blurring image
Filtering image
Rotating image
Sharpening image
In the Test
class of Example 12-2,
the ImageProcessor
is instantiated and effects are
added. If the user chooses to blur the image before filtering the
image, it is a simple matter to add the delegates to the array in the
appropriate order. Similarly, any given operation can be repeated as
often as the user desires, just by adding more delegates to the
collection.
You can imagine displaying the order of operations in a list box that might allow the user to reorder the methods, moving them up and down the list at will. As the operations are reordered you need only change their sort order in the collection. You might even decide to capture the order of operations to a database and then load them dynamically, instantiating delegates as dictated by the records you’ve stored in the database.
Delegates provide the flexibility to determine dynamically which methods will be called, in what order, and how often.
At times it is desirable to multicast
:
to call two implementing methods through a single delegate. This
becomes particularly important when handling events (discussed later
in this chapter).
The goal is to have a single delegate that invokes more than one method. This is different from having a collection of delegates, each of which invokes a single method. In the previous example, the collection was used to order the various delegates. It was possible to add a single delegate to the collection more than once and to use the collection to reorder the delegates to control their order of invocation.
With multicasting you create a single delegate that will call multiple encapsulated methods. For example, when a button is pressed you might want to take more than one action. You could implement this by giving the button a collection of delegates, but it is cleaner and easier to create a single multicast delegate.
Any delegate that returns void
is a multicast
delegate, though you can treat it as a single-cast delegate if you
wish. Two multicast delegates can be combined with the addition
operator (+
). The result is a new multicast
delegate that invokes both of the original implementing methods. For
example, assuming Writer
and
Logger
are delegates that return
void
, the following line will combine them and
produce a new multicast delegate named
myMulticastDelegate
:
myMulticastDelegate = Writer + Logger;
You can add delegates to a multicast delegate using the plus-equals
(+=
) operator. This operator adds the delegate on
the right side of the operator to the multicast delegate on the left.
For example, assuming Transmitter
and
myMulticastDelegate
are delegates, the following
line adds Transmitter
to
myMulticastDelegate
:
myMulticastDelegate += Transmitter;
To see how multicast delegates are created and used, let’s walk
through a complete example. In Example 12-3, you
create a class called MyClassWithDelegate
which
defines a delegate that takes a string as a parameter and returns
void
:
public delegate void StringDelegate(string s);
You then define a class called MyImplementingClass
which has three methods, all of which return void
and take a string as a parameter: WriteString
,
LogString
, and TransmitString
.
The first writes the string to standard output, the second simulates
writing to a log file, and the third simulates transmitting the
string across the Internet. You instantiate the delegates to invoke
the appropriate methods:
Writer("String passed to Writer "); Logger("String passed to Logger "); Transmitter("String passed to Transmitter ");
To see how to combine delegates, you create another
Delegate
instance:
MyClassWithDelegate.StringDelegate myMulticastDelegate;
and assign to it the result of “adding” two existing delegates:
myMulticastDelegate = Writer + Logger;
You add to this delegate an additional delegate using the
+=
operator:
myMulticastDelegate += Transmitter;
Finally, you selectively remove delegates using the
-=
operator:
DelegateCollector -= Logger;
Example 12-3. Combining delegates
namespace Programming_CSharp
{
using System;
public class MyClassWithDelegate
{
// the delegate declaration
public delegate void StringDelegate(string s);
}
public class MyImplementingClass
{
public static void WriteString(string s)
{
Console.WriteLine("Writing string {0}", s);
}
public static void LogString(string s)
{
Console.WriteLine("Logging string {0}", s);
}
public static void TransmitString(string s)
{
Console.WriteLine("Transmitting string {0}", s);
}
}
public class Test
{
public static void Main( )
{
// define three StringDelegate objects
MyClassWithDelegate.StringDelegate
Writer, Logger, Transmitter;
// define another StringDelegate
// to act as the multicast delegate
MyClassWithDelegate.StringDelegate
myMulticastDelegate;
// Instantiate the first three delegates,
// passing in methods to encapsulate
Writer = new MyClassWithDelegate.StringDelegate(
MyImplementingClass.WriteString);
Logger = new MyClassWithDelegate.StringDelegate(
MyImplementingClass.LogString);
Transmitter =
new MyClassWithDelegate.StringDelegate(
MyImplementingClass.TransmitString);
// Invoke the Writer delegate method
Writer("String passed to Writer
");
// Invoke the Logger delegate method
Logger("String passed to Logger
");
// Invoke the Transmitter delegate method
Transmitter("String passed to Transmitter
");
// Tell the user you are about to combine
// two delegates into the multicast delegate
Console.WriteLine(
"myMulticastDelegate = Writer + Logger");
// combine the two delegates, the result is
// assigned to myMulticast Delegate
myMulticastDelegate = Writer + Logger;
// Call the delegated methods, two methods
// will be invoked
myMulticastDelegate(
"First string passed to Collector");
// Tell the user you are about to add
// a third delegate to the multicast
Console.WriteLine(
"
myMulticastDelegate += Transmitter");
// add the third delegate
myMulticastDelegate += Transmitter;
// invoke the three delegated methods
myMulticastDelegate(
"Second string passed to Collector");
// tell the user you are about to remove
// the logger delegate
Console.WriteLine(
"
myMulticastDelegate -= Logger");
// remove the logger delegate
myMulticastDelegate -= Logger;
// invoke the two remaining
// delegated methods
myMulticastDelegate(
"Third string passed to Collector");
}
}
}
Output:
Writing string String passed to Writer
Logging string String passed to Logger
Transmitting string String passed to Transmitter
myMulticastDelegate = Writer + Logger
Writing string First string passed to Collector
Logging string First string passed to Collector
myMulticastDelegate += Transmitter
Writing string Second string passed to Collector
Logging string Second string passed to Collector
Transmitting string Second string passed to Collector
myMulticastDelegate -= Logger
Writing string Third string passed to Collector
Transmitting string Third string passed to Collector
In the test portion of Example 12-3, the delegate
instances are defined and the first three (Writer
,
Logger
, and Transmitter
) are
invoked. The fourth delegate, myMulticastDelegate
,
is then assigned the combination of the first two and it is invoked,
causing both delegated methods to be called. The third delegate is
added, and when myMulticastDelegate
is invoked all
three delegated methods are called. Finally,
Logger
is removed, and when
myMulticastDelegate
is invoked only the two
remaining methods are called.
The power of multicast delegates is best understood in terms of
events, discussed in the next section. When an event such as a button
press occurs, an associated multicast delegate can invoke a series of
event handler methods which will
respond
to the event.
18.219.239.118