A delegate is a type used to reference a method.
This allows methods to be assigned to variables and passed as arguments.
The delegate's declaration specifies the method signature, to which objects of the delegate type can refer.
Delegates are by convention named with each word initially capitalized,
followed by Delegate at the end of the name.
A method that matches the delegate's signature can be assigned to a delegate object of this type.
This delegate object will behave as if it was the method itself,
no matter whether 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 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 can be assigned to delegate objects.
An anonymous method is specified by using the delegate keyword, followed by a parameter list, and a method body.
This can simplify the delegate's instantiation,
since a separate method will not have to be defined in order to instantiate the delegate.
C# 3.0 went one step further, and introduced lambda expressions.
They achieve the same goal as anonymous methods, but with a more concise syntax.
A lambda expression is written as a parameter list, followed by the lambda operator, and an expression.
Like the anonymous method, the lambda must match the signature of the delegate it is assigned too.
Typically, the compiler can determine the data type of the parameters from the context,
so they do not need to be specified.
The parentheses may also be left out if the lambda has only one input parameter.
If no input parameters are needed, an empty set of parentheses must be specified.
A lambda expression that only executes a single statement is called an expression lambda.
The expression of a lambda can also be enclosed in curly brackets to allow it to contain multiple statements.
This form is called a statement lambda.
It is 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 are 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, the subtraction or subtraction assignment operators are used.
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 returns a 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 delegate's signature.
However, a method does not have to match the signature exactly.
A delegate object can also refer to a method that has a more derived return type than that 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, two simple classes will be defined.
The first one is a data storage class called PersonDB, which 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 called Client, and makes use of the storage class.
It has a Main method that creates an instance of PersonDB,
and it calls that object's Process method with a method defined in the Client class.
The benefit of this approach is that it allows the implementation of the data storage
to be separated 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.