So putting it in formal way delegates are type that point to functions that follow specific signatures as defined by the delegates. When invoked the delegates invoke all the functions in their invocation list.
Enough of storytelling and talking lets see some code shall we?
public delegate void MyDelegate(int x, int y);
The delegate in C# is created using deleagate keyword. Nothing fancy here. Then comes the delegate name and the signature of the function to which it can point to. The signature of the function is important because it decides if the delegate can point to that function (add that function in its invocation list. We will see this in a moment).Remember the word qualified from the Foo-Bar story above? So this is the signature that makes all the functions qualified for getting added into the invocation list of the delegate. So in the above example of MyDelegate we have created MyDelegate delegate which can point to all the functions that take two int and return void. For example MyDelegate can point to following functions
public void DoSomething(int a, int b)
{
//do something please
}
public void Apple(int a, int b)
{
//This method also takes two int and returns void
}
public void Mango(int a, int b)
{
//name of the method is irrelevent
}
However it cannot point to following function because they don't have the same signature as defined by the delegate MyDelegate.
public bool ValidateCustomer(IValidateCustomer customer)
{
//This method does not retun void and it does not take two ints
//So this MyDelegate cannot point to it.
}
Note : Methods do not have to match the delegate type exactly. We will touch on this in detail later in this post
Hopefully you must have got what delegates are how to create them. Now lets see how a delegate can point to a valid method and what is invocation list.
Our Compiler friend
Simple delegate delaration
public delegate void MyDelegate(int x, int y);
class DelegateLearning
{
}
Decompiled version (ILSpy)
.class public auto ansi sealed Learning.MyDelegate
extends [mscorlib]System.MulticastDelegate
{
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor (
object 'object',
native int 'method'
) runtime managed
{
} // end of method MyDelegate::.ctor
.method public hidebysig newslot virtual
instance void Invoke (
int32 x,
int32 y
) runtime managed
{
} // end of method MyDelegate::Invoke
.method public hidebysig newslot virtual
instance class [mscorlib]System.IAsyncResult BeginInvoke (
int32 x,
int32 y,
class [mscorlib]System.AsyncCallback callback,
object 'object'
) runtime managed
{
} // end of method MyDelegate::BeginInvoke
.method public hidebysig newslot virtual
instance void EndInvoke (
class [mscorlib]System.IAsyncResult result
) runtime managed
{
} // end of method MyDelegate::EndInvoke
} // end of class LinqLearning.MyDelegate
So lets see what is happening here. First we have created a delegate and then we have decompiled the same using ILSpy.
If we look closesly we see that behind the scenes our compiler friend is doing a lot of work for us. delegate keyword is actually converted to a corresponding class by compiler of the same name. The class then inherits from System.MulticastDelegate. So a delegate is nothing but a class that inherits from System.MulticastDelegate which in turns inherit from System.Delegate.
"A MulticastDelegate has a linked list of delegates, called an invocation list, consisting of one or more elements. When a multicast delegate is invoked, the delegates in the invocation list are called synchronously in the order in which they appear." - MSDN
Invocation List
The Invocation list of a delegate is an ordered set of delegates in which each element of the list invokes exactly one of the methods represented by the delegate. An invocation list can contain duplicate methods. During an invocation, methods are invoked in the order in which they appear in the invocation list. A delegate attempts to invoke every method in its invocation list; duplicates are invoked once for each time they appear in the invocation list. Delegates are immutable; once created, the invocation list of a delegate does not change.
Adding a method to invocation list is easy
- Pass method to the ctor of the Delegate type
- Assign method to the delegate
- Use MulticastDelegate.Combine method to add multiple methods
- += is the short hand for Combine
Following program will make it clear :
namespace DelegateLearning
{
public delegate int MathematicalOperationDelegate(int x, int y); //declare
public enum MathematicalOperation
{
Add,
Substract,
Multiply,
Divide
}
class DelegateLearning
{
private readonly MathematicalOperationDelegate _mathematicalOperationDelegate;
public DelegateLearning(MathematicalOperation operation)
{
switch (operation)
{
case MathematicalOperation.Add:
_mathematicalOperationDelegate += new MathematicalOperationDelegate(AddOperationTarget); //Adding to invocation list
break;
case MathematicalOperation.Substract:
_mathematicalOperationDelegate += new MathematicalOperationDelegate(SubOperationTarget); //Adding to invocation list
break;
case MathematicalOperation.Multiply:
_mathematicalOperationDelegate += new MathematicalOperationDelegate(MultiplyOperationTarget); //Adding to invocation list
break;
case MathematicalOperation.Divide:
_mathematicalOperationDelegate += new MathematicalOperationDelegate(DivideOperationTarget); //Adding to invocation list.
break;
}
}
public int PerformMathematicalOperation(int x, int y , MathematicalOperation operation)
{
int result = 0;
if (_mathematicalOperationDelegate != null)
{
result = _mathematicalOperationDelegate(x, y); //Invoke
Console.WriteLine( "Result : " + result);
}
return result;
}
private int AddOperationTarget(int x, int y)
{
return x + y;
}
private int SubOperationTarget(int x, int y)
{
return x - y;
}
private int MultiplyOperationTarget(int x, int y)
{
return x * y;
}
private int DivideOperationTarget(int x, int y)
{
return x / y;
}
}
}
In Main :
public static void Main() {
var learning = new global::DelegateLearning.DelegateLearning(MathematicalOperation.Add);
var result = learning.PerformMathematicalOperation(12, 12);
Console.WriteLine( result );
}
Output : 24
Lets take our time to understand what is happening and why its doing what it is doing. We start by declaring a delegate MathematicalOperationDelegate. This delegate will point to any function/s which takes two int and return int.
Next we create a class DelegateLearning which has one parametrized constructor which takes enum MathematicalOperation. Based on the enum passed, corresponding method will be added to the invocation list of the MathematicalOperationDelegate.
+= can be used to add a method to the invocation list of any delegate. This is shorthand for the public static Delegate Combine(params Delegate[] delegates); method in MulticastDelegate class.
Our class declares a method
PerformMathematicalOperation invokes the delegate as result = _mathematicalOperationDelegate(x, y); //Invoke
At this point all the methods present in the invocation list of the delegate will be executed one by one with parameter value of x and y.
Please note that the null check is required before invoking the delegate otherwise NullReferenceException will be thrown.
But ... Why all this?
The question in your mind will be why all this is required. We could have called methods directly and could have got the same result. Yes you are right, maybe in this case you could have done it. But hey there is Hello World in every language!
The caller can decide the method @ runtime
The advantage of delegates is that now caller gets opportunity to decide the method implementation at runtime. Its quiet similar to Interfaces. So if a developer is creating an API for Mathematical operation, he can just provide a delegate with signature and user of the API can decide his/her own implementation. For example in the above example I can create a method that takes two int and provide my custom implementation. My DelegateLearning class doesn't have to worry about the calulation alogrithm. It therefore provides and better seperation of concern.
Delegates are useful when:
- A single method is being called.
- A class may want to have multiple implementations of the method specification.
- It is desirable to allow using a static method to implement the specification.
- An event-like design pattern is desired.
- The caller has no need to know or obtain the object that the method is defined on.
- The provider of the implementation wants to "hand out" the implementation of the specification to only a few select components.
- Easy composition is desired.
Before we end:
Variance in Delegates
- Covariance permits a method to have return type that is more derived than that defined in the delegate.
- Contravariance permits a method that has parameter types that are less derived than those in the delegate type.
class Person { }
class Man : Person { }
class Program
{
// Define the delegate.
public delegate Person HandlerMethod();
public delegate Person HandlerMehtod2(Man p);
public static Person PersonHandler()
{
return null;
}
public static Man ManHandler()
{
return null;
}
public static Man ManHandler2(Person person)
{
return null;
}
static void Test()
{
HandlerMethod handlerPerson = PersonHandler;
// Covariance enables this assignment.
HandlerMethod handlerMan = ManHandler;
//Contravariance enables this assignment.
HandlerMehtod2 handlerMehtod = ManHandler2;
ManHandler2(new Man());
}
}
This example demonstrates how delegates can be used with methods that have return types that are derived from the return type in the delegate signature. The data type returned by ManHandler is of type Man, which derives from Person type that is defined in the delegate.
This post covered delegates and in general. In next post I will cover anonymous methods and Lambdas which makes delegates more fun!
I hope you liked it. Do correct me if I have made any mistakes.
Till then next time happy coding :)
References:
https://msdn.microsoft.com/en-in/library/aa288459(v=vs.71).aspx