A delegate is a type used to reference a method. The delegate’s declaration specifies the
method signature that objects of the delegate type can refer to. If we define a method that
matches this signature we can create a delegate object from the type and assign the method
to it. This delegate object now behaves as if it was the method itself, no matter if
it refers to a static or an instance method. A method call on the object will be forwarded
by the delegate to the method and any return value will be passed back through the delegate.
The syntax used here to instantiate the delegate is actually a simplified notation that was
introduced in C# 2.0. The backwards compatible way to instantiate a delegate is to use the
normal reference type initialization syntax. C# 2.0 also introduced anonymous methods,
which allow us to create a delegate by specifying the delegate keyword followed by a method’s
parameter list and body. This can simplify the delegate’s instantiation since it doesn’t
require a separate method in order to implement the delegate's functionality.
It’s possible for a delegate object to refer to more than one method. Such an object is
known as a multicast delegate and the methods it refers to is contained in a so called invocation
list. To add another method to the delegate's invocation list either the addition operator
or the addition assignment operator can be used. Similarly, to remove a method from the
invocation list we use the subtraction or subtraction assignment operator. When calling
a multicast delegate object all methods in its invocation list will be invoked with the
same arguments in the order that they were added to the list. If the delegate has a return
value only the value of the last invoked method will be returned. Likewise, if the delegate
has an out parameter its final value will be the value assigned by the last method.
As mentioned before, a method can be assigned to a delegate object if it matches the delegates
signature. However, a method doesn’t have to match the signature exactly. A delegate
object can also refer to a method that has a more derived return type then what is defined
in the delegate. Or that has parameter types that are ancestors of the corresponding delegate’s
parameter types.
An important property of delegates is that they can be passed as method parameters. To
demonstrate the benefit of this let’s define two simple classes. The first one is a data
storage class called PersonDB that has an array containing a couple of names. It also
has a method that takes a delegate object as its argument and calls that delegate for
each name in the array. The second class is the client that uses the storage class. It
has a Main method that creates an instance of PersonDB and calls that object’s Process
method with a method that is defined in the Client class.
The benefit of this approach is that it allows us to separate the implementation of the data
storage from the implementation of the data processing. The storage class only handles
the storage and has no knowledge of the processing that is done on the data. This allows the
storage class to be written in a more general way than if this class had to implement all
of the potential processing operations that a client may want to perform on the data.
With this solution, the client can simply plug its own processing code into the existing
storage class.