Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Delegates Tutorial - MC++ and C# - The dual perspective

0.00/5 (No votes)
12 Jun 2002 1  
Introduces and walks you through the use of delegates in .NET. Compares and contrasts the different approaches that MC++ and C# adopt in the use of delegates. With examples in both languages

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 declaration using Managed C++

__delegate String* DelegateAbc(); 
//delegate declaration using C#

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.

//binding delegates using MC++

DelegateAbc *d1 = new DelegateAbc(t1,&Test::TestAbc); //instance method

DelegateAbc *d2 = new DelegateAbc(0,&Test::TestStatic); //static method
//binding delegates using C#

DelegateAbc d1 = new DelegateAbc (t1.TestAbc); //instance method

DelegateAbc d2 = new DelegateAbc (Test.TestAbc); //static method

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.

//multi-cast delegate using MC++

d1 = static_cast<DelegateAbc*> (Delegate::Combine(d1,
         new DelegateAbc(t1,&Test::TestAbc)));
//multi-cast delegate using C#

d1 = d2 + new DelegateAbc (Test.TestAbc); //using the + operator

d1 += new DelegateAbc (Test.TestAbc); //using the += operator 

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.

//removing a delegate from a multi-cast delegate - MC++

d1 = static_cast<DelegateAbc*>(Delegate::Remove(d1,d2));
//removing a delegate from a multi-cast delegate - C#

d1 = d1 - d2; //using the - operator

d1 -= d3; //using the -= operator

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.

//invoking a delegate with MC++

d1->Invoke("4"); //passing a string as argument

d2->Invoke(); //no arguments
//invoking a delegate with C#

d1("4");  //passing a string as argument

d2(); //no arguments

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.

/* Managed C++ Sample */

#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;
}
/* C# Sample */

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.

/* Managed C++ Sample */

#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;
}
/* C# Sample */

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.

/* Managed C++ Sample */

#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);
}
/* C# Sample */

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here