Introduction
One of my favorite features about good old C was function pointers. Those of
you who haven't used function pointers missed out on the fun. When C++ was out
we also had pointers to member functions. The basic problem with function
pointers and pointers to member functions is that, neither of them is type-safe.
The .NET framework has a class named Delegate
in the System
namespace. Delegates
are the .NET surrogate for function pointers and pointers to member functions.
The advantage with delegates is that delegates are fully managed objects that
are also type safe. A delegate basically encapsulates a method with a particular
set of arguments and return type. You can encapsulate only a method that matches
the delegate definition in a delegate. Delegates can encapsulate both static
methods of a class as well as instance methods.
Delegates are called single-cast delegates when they encapsulate a single
method, and are called multi-cast delegates when they encapsulate more than one
method. Multi-cast delegates are useful as event-handlers. Multi-cast delegates
should not be confused with an array of delegates. Multi-cast delegates are
derived from the MulticastDelegate
class which is a child class of the Delegate
class. When a multi-cast delegate is invoked, the encapsulated methods are
called synchronously in the same order in which they were added to the
multi-cast delegate. Managed C++ and C# offer language specific features that
allow us to work with delegates directly without having to call member methods
of the Delegate class or the MulticastDelegate
class. Through some very simple
examples, I'll show how delegates are used, both in Managed C++ and in C#.
Basic operations
Declaring delegates
In Managed C++ we use the __delegate
keyword to declare delegates. In C# we
use the delegate
keyword. In both cases the compiler will automatically inherit
from System::Delegate
. There is no difference in the manner of declaration
between single-cast and multi-cast delegates. I presume that internally a
single-cast delegate is treated as a multi-cast delegate with just one
encapsulated method.
__delegate String* DelegateAbc();
public delegate String DelegateAbc();
Binding delegates to methods
For a single-cast delegate we simple use the default delegate constructor
which the delegates inherit from System::Delegate
. The constructor takes two
arguments, where the first argument is the object whose method we are binding to
the delegate and the second argument is the address of the method. For static
methods the first argument can be 0. In C# things are simplified further in that
we don't need to pass the first argument. The C# compiler figures it out for us.
DelegateAbc *d1 = new DelegateAbc(t1,&Test::TestAbc);
DelegateAbc *d2 = new DelegateAbc(0,&Test::TestStatic);
DelegateAbc d1 = new DelegateAbc (t1.TestAbc);
DelegateAbc d2 = new DelegateAbc (Test.TestAbc);
For multi-cast delegates we use the Delegate.Combine
method which has two
overloads. One overload takes an array of Delegate
objects and combines them. The
other overload takes two Delegate
objects and combines them. Both return a
Delegate
object which we need to cast to our delegate type. Again, C#
programmers have it really easy. The + and += operators has been overloaded in
C# and adding a delegate to another delegate is done simply by using the +
operator on any number of delegates.
d1 = static_cast<DelegateAbc*> (Delegate::Combine(d1,
new DelegateAbc(t1,&Test::TestAbc)));
d1 = d2 + new DelegateAbc (Test.TestAbc);
d1 += new DelegateAbc (Test.TestAbc);
For removing a delegate from the invocation list of a multi-cast delegate we
use the Delegate.Remove
method. This method takes two arguments. The first
argument is the source delegate which may contain one or more encapsulated
methods. The second argument is the delegate object that we wish to remove from
the multi-cast delegate. The method returns a Delegate
object which we cast to
the delegate type we are expecting. I guess you might have guessed by now that
C# would have a simpler way of doing things. In C# the - and -= operators have
been overloaded so that you can actually subtract a delegate from a multicast
delegate.
d1 = static_cast<DelegateAbc*>(Delegate::Remove(d1,d2));
d1 = d1 - d2;
d1 -= d3;
Invoking a delegate
When we invoke a delegate, the encapsulated methods are synchronously called
in the order in which they were attached to the delegate. In Managed C++ this is
achieved by calling a method called Invoke
. This method is added to our delegate
class by the compiler and will have the same signature as our delegate. In C#,
we need not bother even this much, and all we have to do is to call a method
that has the same name as our delegate object, and pass it any required
arguments. The Invoke
mechanism described here is based on early binding. We
know exactly what the delegate signature is and thus we can invoke our delegate.
It might interest you to know that the Invoke method is actually added by the
respective compilers and is not inherited from the Delegate class. For late
bound invocation you can use the DynamicInvoke
method. But this article will not
cover late bound invocation as it's outside the scope and latitude of this
article.
d1->Invoke("4");
d2->Invoke();
d1("4");
d2();
Now we'll see some small sample programs that will make things clearer to
you. Compile and run the programs and try and figure out whether the output you
get makes sense. If you are confused, don't worry too much, just read the
article once more and then think about it for some time. Things will slowly make
sense. There are also some good articles on MSDN dealing with delegates which
will enlighten you further.
Program 1
In this program we'll see how to declare and use a single-cast delegate. Our
delegate takes a String
as argument and returns a String
as well. We'll first
assign an instance method of an object to the delegate and then invoke the
delegate. Then we'll assign a static method of a class to the same delegate
object and again invoke the delegate.
#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;
__delegate String* DelegateAbc(String* txt);
__gc class Test
{
public:
String* TestAbc(String* txt)
{
Console::WriteLine(txt);
return "Hello from TestAbc";
}
static String* TestStatic(String* txt)
{
Console::WriteLine(txt);
return "Hello from TestStatic";
}
};
int wmain(void)
{
Test *t1 = new Test();
DelegateAbc *d1 = new DelegateAbc(t1,&Test::TestAbc);
Console::WriteLine(d1->Invoke("First call"));
d1 = new DelegateAbc(0,&Test::TestStatic);
Console::WriteLine(d1->Invoke("Second call"));
return 0;
}
using System;
class DelegateDemo
{
delegate String DelegateAbc(String txt);
public String TestAbc(String txt)
{
Console.WriteLine(txt);
return "Hello from TestAbc";
}
public static String TestStatic(String txt)
{
Console.WriteLine(txt);
return "Hello from TestStatic";
}
static void Main()
{
DelegateDemo t1 = new DelegateDemo();
DelegateAbc d1 = new DelegateAbc(t1.TestAbc);
Console.WriteLine(d1("First call"));
d1 = new DelegateAbc(DelegateDemo.TestStatic);
Console.WriteLine(d1("Second call"));
}
}
Program 2
Now we'll see an example of using a multi-cast delegate. Our delegate takes
zero arguments and returns void
. We'll first create two single-cast delegates,
one based on an instance method and the other one based on a static method. Then
we'll create our multi-cast delegate by combining the two delegates. Now we
invoke our multi-cast delegate. From the output you should be able to figure out
the order in which the encapsulated methods were called. Now we remove one of
the delegates from our multi-cast delegate and again invoke it. The output
should match your understanding of the working of delegates.
#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;
__delegate void DelegateAbc();
__gc class Test
{
public:
void TestAbc()
{
Console::WriteLine("This is from TestAbc");
}
static void TestStatic()
{
Console::WriteLine("This is from the static method");
}
};
int wmain(void)
{
Test *t1 = new Test();
DelegateAbc *d1 = new DelegateAbc(t1,&Test::TestAbc);
DelegateAbc *d2 = new DelegateAbc(0,&Test::TestStatic);
d1 = static_cast<DelegateAbc*> (Delegate::Combine(d1,d2));
d1->Invoke();
d1 = static_cast<DelegateAbc*>(Delegate::Remove(d1,d2));
Console::WriteLine();
d1->Invoke();
return 0;
}
using System;
class DelegateDemo
{
delegate void DelegateAbc();
public void TestAbc()
{
Console.WriteLine("This is from TestAbc");
}
public static void TestStatic()
{
Console.WriteLine("This is from the static method");
}
static void Main()
{
DelegateDemo t1 = new DelegateDemo();
DelegateAbc d1 = new DelegateAbc(t1.TestAbc);
DelegateAbc d2 = new DelegateAbc(DelegateDemo.TestStatic);
d1 = d1+d2;
d1();
d1 -= d2;
Console.WriteLine();
d1();
}
}
Program 3
In this program we will see how we can pass a delegate object as an argument to a
method. The groovy thing about this is that the called method has absolutely no
idea what the passed delegate is referencing. In our little example we have a
delegate that takes an int
and returns an int
. We'll write two methods
that can be assigned to the delegate, one that returns the square of the passed
number and the other that returns the cube of the passed number.
#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;
__delegate int DelegateAbc(int);
__gc class Test
{
public:
int SquareMe(int i)
{
return i*i;
}
int CubeMe(int i)
{
return i*i*i;
}
void ShowResult(DelegateAbc* d, String* s,int i)
{
Console::WriteLine("{0} of {1} is {2}",s,
i.ToString(),d->Invoke(i).ToString());
}
};
int wmain(void)
{
Test *t = new Test();
t->ShowResult(new DelegateAbc(t,&Test::SquareMe),"Square",7);
t->ShowResult(new DelegateAbc(t,&Test::CubeMe),"Cube",7);
}
using System;
class DelegateDemo
{
delegate int DelegateAbc(int i);
public int SquareMe(int i)
{
return i*i;
}
public int CubeMe(int i)
{
return i*i*i;
}
void ShowResult(DelegateAbc d, String s,int i)
{
Console.WriteLine("{0} of {1} is {2}",s,i,d(i));
}
static void Main()
{
DelegateDemo t = new DelegateDemo();
t.ShowResult(new DelegateAbc(t.SquareMe),"Square",7);
t.ShowResult(new DelegateAbc(t.CubeMe),"Cube",7);
}
}
Conclusion
Well, summing up, a delegate is just about the equivalent of function
pointers except that delegates are objects and are type safe. Unlike function
pointers delegates can reference both static and instance methods of a class.
Delegates inherit from MulticastDelegate
. The compiler adds an Invoke
method to
your delegate object, which has the same signature and return type as the
delegate. Delegates can be single-cast or multi-cast. Multi-cast delegates are
formed by combining several delegates. Delegates can be passed as arguments to
functions.
The great thing
about delegates is that they don't care about the class whose member function
they are referencing. All it cares about is that the arguments passed and the
return type match that of its own. We can thus use delegates for
black-box-invocation, where we don't know what member function the delegate is
pointing to. Delegates are very useful as event handlers. When an event is
raised the event handlers of the subscribing classes are invoked through
delegates.