Home

Delegates

 

Introduction

As a variable can be declared as a pointer so can a function. As we saw in the previous sections, a pointer to a function is a type of variable whose name can be used as a variable although it is not a traditional variable like the others. This concept has always been very helpful in Microsoft Windows programming because it allows the use of callback functions. Thanks to their effectiveness (and legacy code), the concept of callback functions was carried out in the .NET Framework but they were defined with the name of delegate.

A delegate is a special type of user-defined variable that references a method of a class. There are similarities and differences between a function pointer and a delegate:

  • Like a function pointer, a delegate is not defined like a normal function
  • Like a function pointer, a delegate has the appearance of a variable that is declared as a pointer
  • Like a function pointer, a delegate can take argument(s)
  • While a function pointer can be associated with a global function, that is, a function that is not a member of a class, a delegate can be associated with, or can reference, only a member of a class

Practical LearningPractical Learning: Introducing Delegates

  1. Start Microsoft Visual C++ 2005
  2. On the main menu, click File -> New -> Project...
  3. On the left side, make sure that Visual C++ is selected. In the Templates list, click CLR Empty Project
  4. In the Name box, replace the string with FlowerShop1 and click OK
  5. On the main menu, click Project -> Add Class...
  6. In the Categories lists, expand Visual C++ and click C++.
    In the Templates list, make sure C++ Class is selected and click Add
  7. Set the Name of the class to CFlower and click Finish
  8. Complete the Flower.h header file as follows:
     
    #pragma once
    using namespace System;
    
    public ref class CFlower
    {
    private:
        String ^ _tp;
        String ^ _clr;
        String ^ _arg;
        double   _price;
    
    public:
        property String ^ Type
        {
    	String ^ get() { return _tp; }
    	void set(String ^ tp) { _tp = tp; }
        }
    
        property String ^ Color
        {
    	String ^ get() { return _clr; }
    	void set(String ^ clr) { _clr = clr; }
        }
    
        property String ^ Arrangement
        {
    	String ^ get() { return _arg; }
    	void set(String ^ arg) { _arg = arg; }
        }
    
        property double UnitPrice
        {
    	double get() { return _price; }
    	void set(double price) { _price = price; }
        }
    
    public:
        CFlower(void);
        CFlower(String ^ type, String ^ color,
    	    String ^ argn, double price);
        ~CFlower();
    
        void Show();
    };
  9. Complete the Flower.cpp source file as follows:
     
    #include "Flower.h"
    
    CFlower::CFlower(void)
        : _tp(L"Roses"), _clr(L"Red"),
          _arg(L"Basket"), _price(45.95)
    {
    }
    
    CFlower::CFlower(String ^ type, String ^ color,
    		 String ^ argn, double price)
    {
        _tp = type;
        _clr = color;
        _arg = argn;
        _price = price;
    }
    
    CFlower::~CFlower()
    {
    }
    
    void CFlower::Show()
    {
        Console::WriteLine(L"=======================");
        Console::WriteLine(L"==-=-=Flower Shop=-=-==");
        Console::WriteLine(L"-----------------------");
        Console::WriteLine(L"Flower Type:  {0}", Type);
        Console::WriteLine(L"Flower Color: {0}", Color);
        Console::WriteLine(L"Arrangement:  {0}", Arrangement);
        Console::WriteLine(L"Price:        {0:C}", UnitPrice);
        Console::WriteLine(L"=======================");
    }
  10. To create one more source file, on the main menu, click Project -> Add New Item...
  11. In the Templates list, make sure C++ File (.cpp) is selected.
    Set the Name to Exercise and click Add
  12. Complete the file as follows:
     
    #include "Flower.h"
    using namespace System;
    
    int main()
    {
        CFlower ^ flower = gcnew CFlower(L"Lilies", L"Pink",
    				     L"Bouquet", 45.65);
        flower->Show();
    
        Console::WriteLine();
        return 0;
    }
  13. Execute the application to make sure it can compile:
     
    =======================
    ==-=-=Flower Shop=-=-==
    -----------------------
    Flower Type:  Lilies
    Flower Color: Pink
    Arrangement:  Bouquet
    Price:        $45.65
    =======================
    
    Press any key to continue . . .
  14. Close the DOS window

Delegate Declaration 

To declare a delegate, you use the delegate keyword. The basic formula used to create a delegate is:

Access Level delegate Function-Signature;

The Function-Signature factor is created as you would declare a normal C++ function. This means that the Function-Signature must have

  1. A return type: The return type can be void, one of the primitive data types (int, long, double, short, etc), a .NET Framework type (Double, Decimal, etc), a .NET Framework class, or a class you created
  2. A name: Like every variable, function, or class, a delegate must have a name
  3. Parentheses: Like every function, a delegate must have parentheses
  4. Optional arguments: If the method(s) that this delegate will reference has/have argument(s), this delegate must also have argument(s) of exactly the same kind

After defining the Function-Signature, you must end the delegate declaration with a semi-colon. Here is an example:

delegate double Addition();

A declaration of a delegate can also use an access level as public or private. If you plan to use the delegate only locally, you can omit the access level or declare it as private, which is the default. If you plan to use the delegate outside of the project in which it is created, you should declare it as public.

After declaring a delegate, remember that it only provides a skeleton for a method, not an actual method. In order to use it, you must define a method that would carry an assignment the method is supposed to perform. That method must have the same return type and the same (number of) argument(s), if any. Here is an example:

private delegate double Addition();

public ref class CMathOperations
{
public:
    double Plus()
    {
	double a = 248.66, b = 50.28;

	return a + b;
    }
};

After implementing the method, you can associate it to the name of the delegate. To do that, where you want to use the method, first declare a pointer of the type of the delegate using the gcnew operator and calling its default constructor.

Practical LearningPractical Learning: Creating a Delegate

  1. To create a delegate, declare the following in the Exercise.cpp file:
     
    #include "Flower.h"
    using namespace System;
    
    private delegate void Display();
    
    int main()
    {
        return 0;
    }
  2. Save the file

Techniques of Using a Delegate

After properly declaring and initializing the instance, it becomes ready and you can use it. When you declare a delegate, at run time, the compiler generates a class for it. The generated class automatically receives the following characteristics:

  • The class is derived from the System::MulticastDelegate class which itself derives from the System::Delegate class. Both classes are defined in the System.dll assembly
  • The class is equipped with a default constructor and two other constructors that takes two arguments as follows:
      
    protected:
        MulticastDelegate();
    
    protected:
        MulticastDelegate(Object^ target, String^ method);
    
    protected:
        MulticastDelegate(Type^ target, String^ method);
  • The class is automatically equipped with a method called Invoke. Its return type and its argument become the same as those of the declared delegate

As mentioned in the second point, the instance of the delegate can take two arguments. If you declared the method as done earlier (delegate double Addition();), the first argument of the instance must be a pointer to the class that implements the method associated with the delegate. This would be done as follows:

using namespace System;

private delegate double Addition();

public ref class CMathOperations
{
public:
    double Plus()
    {
	double a = 248.66, b = 50.28;

	return a + b;
    }
};

int main()
{
    CMathOperations ^ oper = gcnew CMathOperations;
    Addition ^ add = gcnew Addition(oper, . . .);
    
    return 0;
}

After the first argument, the second argument must be a reference to the class' method that implements the desired behavior of the delegate. Here is an example:

using namespace System;

private delegate double Addition();

public ref class CMathOperations
{
public:
    double Plus()
    {
	double a = 248.66, b = 50.28;

	return a + b;
    }
};

int main()
{
    CMathOperations ^ oper = gcnew CMathOperations;
    Addition ^ add = gcnew Addition(oper, &CMathOperations::Plus);
    
    return 0;
}

The declaration gives meaning to the delegate. To actually use the method, you can call the name of the delegate as if it were a defined method. If you want to retrieve the value that the delegate produces, in the above third point, we mentioned that the delegate's class is equipped with a method called Invoke that completely "understands" the method that implements it. This means that the Invoke() method returns the same value as the associated method. In our example, the Invoke() method of the delegate would return a double since the delegate was declared as returning a double.

Here is an example that uses the delegate:

using namespace System;

private delegate double Addition();

public ref class CMathOperations
{
public:
    double Plus()
    {
	double a = 248.66, b = 50.28;

	return a + b;
    }
};

int main()
{
    CMathOperations ^ oper = gcnew CMathOperations;
    Addition ^ add = gcnew Addition(oper, &CMathOperations::Plus);

    Console::WriteLine(L"Value = {0}", add->Invoke());
    return 0;
}

This would produce:

Value = 298.94
Press any key to continue . . .

We mentioned earlier that you could declare the associated method of a delegate like any normal method, in which case you must pass a pointer to the class as the first argument of the instance of the delegate. This is a requirement if you plan to instantiate the delegate outside of the class that defines its associated method. That's the case for our MathOperations class. If you plan to use a delegate in the same class that implements its associated method, you can declare the method static. Here is an example:

private delegate double Addition();
private delegate double Multiplication();

public ref class CMathOperations
{
public:
    double Plus()
    {
	double a = 248.66, b = 50.28;

	return a + b;
    }

    static double Times()
    {
	double a = 248.66, b = 50.28;

	return a * b;
    }
};

Once again, to use the delegate, you must first declare its pointer and allocate its memory using the gcnew operator and calling the constructor that takes two arguments. This time, the first argument must be null or 0. The second argument must still be a reference to the method that implements the desired behavior. Here is an example:

using namespace System;

private delegate double Addition();
private delegate double Multiplication();

public ref class CMathOperations
{
public:
    CMathOperations()
    {
        multi = gcnew Multiplication(&Times);
    }

    double Plus()
    {
	double a = 248.66, b = 50.28;

	return a + b;
    }

    static double Times()
    {
	double a = 248.66, b = 50.28;

	return a * b;
    }

    void ShowResult()
    {
	Console::WriteLine(L"Multiplication = {0}", multi->Invoke());
    }

private:
    Multiplication ^ multi;
};

int main()
{
    CMathOperations ^ oper = gcnew CMathOperations;
    Addition ^ add = gcnew Addition(oper, &CMathOperations::Plus);

    Console::WriteLine(L"Addition = {0}", add->Invoke());
    oper->ShowResult();

    return 0;
}

This would produce:

Addition = 298.94
Multiplication = 12502.6248
Press any key to continue . . .

Because delegates are usually declared globally, that is, outside of a class, they can be associated with a method of any class, provided the method has the same return type (and the same (number of) argument(s)) as the delegate.

Practical LearningPractical Learning: Creating a Delegate

  1. To implement and use a delegate, change the Exercise.cpp source file as follows:
     
    #include "Flower.h"
    using namespace System;
    
    private delegate void Display();
    
    int main()
    {
        CFlower ^ flower = gcnew CFlower;
    	
        flower->Type = L"Daisies";
        flower->Color = L"Yellow";
        flower->Arrangement = L"Vase";
        flower->UnitPrice = 45.65;
    
        Display ^ disp = gcnew Display(flower, &CFlower::Show);
        disp->Invoke();
    
        Console::WriteLine();
        return 0;
    }
  2. Execute the application to see the result:
     
    =======================
    ==-=-=Flower Shop=-=-==
    -----------------------
    Flower Type:  Daisies
    Flower Color: Yellow
    Arrangement:  Vase
    Price:        $45.65
    =======================
    
    Press any key to continue . . .
  3. Close the DOS window

Delegates Compositions

One of the characteristics that set delegates apart from C/C++ function pointers is that one delegate can be added to another using the overloaded addition assignment operator. This is referred to as composition.

A Delegate With One of More Arguments

If you want to associate a method that takes arguments to a delegate, when declaring the delegate, provide the necessary argument(s) in its parentheses. Here is an example of a delegate that takes two arguments (and returns a value):

delegate double Addition(double x, double y);

When defining the associated method, besides returning the same type of value if not void, make sure that the method takes the same number of arguments. Here is an example:

private delegate double Addition(double x, double y);
private delegate double Multiplication(double x, double y);

public ref class CMathOperations
{
public:
    double Plus(double x, double y)
    {
	return x + y;
    }
};

Once again, to associate the method, declare a variable of the type of delegate and pass a reference to the method as the second argument to the constructor of the delegate. Here is an example:

private delegate double Addition(double x, double y);
private delegate double Multiplication(double x, double y);

public ref class CMathOperations
{
public:
    CMathOperations()
    {
        add = gcnew Addition(&Plus);
        multi = gcnew Multiplication(&Times);
    }

    static double Plus(double x, double y)
    {
	return x + y;
    }

    static double Times(double x, double y)
    {
        return x * y;
    }

private:
    Addition ^add;
    Multiplication ^ multi;
};

Notice that only the name of the method is passed to the delegate. To actually use the value that the delegate produces, when calling its Invoke() method, you must pass the exact number of arguments with their correct values:

using namespace System;

private delegate double Addition(double x, double y);
private delegate double Multiplication(double x, double y);

public ref class CMathOperations
{
public:
    CMathOperations()
    {
        add = gcnew Addition(&Plus);
        multi = gcnew Multiplication(&Times);
    }

    static double Plus(double x, double y)
    {
	return x + y;
    }

    static double Times(double x, double y)
    {
        return x * y;
    }

    void ShowResult()
    {
	Console::WriteLine(L"Addition = {0}", add->Invoke(24.56, 98.76));
	Console::WriteLine(L"Multiplication = {0}", multi->Invoke(24.56, 98.76));
    }

private:
    Addition ^add;
    Multiplication ^ multi;
};

int main()
{
    CMathOperations ^ oper = gcnew CMathOperations;
   
    oper->ShowResult();

    return 0;
}

This would produce:

Addition = 123.32
Multiplication = 2425.5456
Press any key to continue . . .

Practical LearningPractical Learning: Using an Argumentative Delegate

  1. To add an argument to a method, change the Flower.h header file as follows:
     
    #pragma once
    using namespace System;
    
    public ref class CFlower
    {
    private:
        String ^ _tp;
        String ^ _clr;
        String ^ _arg;
        double   _price;
    
    public:
        . . .
    
    public:
        CFlower(void);
        CFlower(String ^ type, String ^ color,
    	        String ^ argn, double price);
        ~CFlower();
    
        void Show(const int qty);
    };
  2. To implement the method, change the Flower.cpp source file as follows:
     
    #include "Flower.h"
    
    . . .
    
    void CFlower::Show(const int qty)
    {
        double totalPrice = UnitPrice * qty;
    
        Console::WriteLine(L"=======================");
        Console::WriteLine(L"==-=-=Flower Shop=-=-==");
        Console::WriteLine(L"-----------------------");
        Console::WriteLine(L"Flower Type:  {0}", Type);
        Console::WriteLine(L"Flower Color: {0}", Color);
        Console::WriteLine(L"Arrangement:  {0}", Arrangement);
        Console::WriteLine(L"Price:        {0:C}", UnitPrice);
        Console::WriteLine(L"Quantity:     {0}", qty);
        Console::WriteLine(L"Total Price:  {0:C}", totalPrice);
        Console::WriteLine(L"=======================");
    }
  3. To use a delegate with argument, change the Exercise.cpp source file as follows:
     
    #include "Flower.h"
    using namespace System;
    
    private delegate void Display(const int x);
    
    int main()
    {
        CFlower ^ flower = gcnew CFlower;
    	
        flower->Type        = L"Mixed";
        flower->Color       = L"Various";
        flower->Arrangement = L"Bouquet";
        flower->UnitPrice   = 34.85;
    
        Display ^ disp = gcnew Display(flower, &CFlower::Show);
        disp->Invoke(3);
    
        Console::WriteLine();
        return 0;
    }
  4. Execute the application to see the result:
     
    =======================
    ==-=-=Flower Shop=-=-==
    -----------------------
    Flower Type:  Mixed
    Flower Color: Various
    Arrangement:  Bouquet
    Price:        $34.85
    Quantity:     3
    Total Price:  $104.55
    =======================
    
    Press any key to continue . . .
  5. Close the DOS window

A Delegate Passed as Argument

Using delegates, one method can be indirectly passed as argument to another method. To proceed, first declare the necessary delegate. Here is a example of such a delegate:

public delegate double Squared(double x);

A delegate can be passed as argument to a method. Such an argument would be used as if it were a method itself. This means that, when accessed in the body of the method, the name of the delegate must be accompanied by parentheses and if the delegate takes an argument or arguments, the argument(s) must be provided in the parentheses of the called delegate. Here is an example:

public delegate double Squared(double x);

public ref class CCircle
{
private:
    double _radius;

public:
    double Area(Squared ^ sqd)
    {
	return sqd(_radius) * Math::PI;
    }

public:
    property double Radius
    {
        double get() { return _radius; }
        void   set(double r) { _radius = r; }
    }
};

After declaring a delegate, remember to define a method that implements the needed behavior of that delegate. Here is an example:

public delegate double Squared(double x);

public ref class CCircle
{
private:
    double _radius;

public:
    double Area(Squared ^ sqd)
    {
	return sqd(_radius) * Math::PI;
    }

    static double ValueTimesValue(double Value)
    {
	return Value * Value;
    }

public:
    property double Radius
    {
        double get() { return _radius; }
        void   set(double r) { _radius = r; }
    }
};

You can also define the associated method in another class, not necessarily in the class where the delegate would be needed. Once the method that implements the delegate is known, you can use the delegate as you see fit. To do that, you can declare a pointer of the type of that delegate and pass the implementing method to its constructor. Here is an example:

public delegate double Squared(double x);

public ref class CCircle
{
private:
    double _radius;

public:
    double Area(Squared ^ sqd)
    {
	return sqd(_radius) * Math::PI;
    }

    static double ValueTimesValue(double Value)
    {
	return Value * Value;
    }

public:
    property double Radius
    {
        double get() { return _radius; }
        void   set(double r) { _radius = r; }
    }

    void CircleCharacteristics()
    {
	Squared ^ Sq = gcnew Squared(ValueTimesValue);
    }
};

This declaration gives life to the delegate and can then be used as we have proceed with delegates so far. Here is an example:

using namespace System;

public delegate double Squared(double x);

public ref class CCircle
{
private:
    double _radius;

public:
    
    double Area(Squared ^ sqd)
    {
	return sqd(_radius) * Math::PI;
    }

    static double ValueTimesValue(double Value)
    {
	return Value * Value;
    }

public:
    property double Radius
    {
        double get() { return _radius; }
        void   set(double r) { _radius = r; }
    }
};

int main()
{
    CCircle ^ round = gcnew CCircle;
    Squared ^ sqr   = gcnew Squared(CCircle::ValueTimesValue);

    round->Radius = 32.48;
    Console::WriteLine(L"Circle Characteristics");
    Console::WriteLine(L"Radius: {0}", round->Radius);
    Console::WriteLine(L"Area:   {0}", round->Area(sqr));

    return 0;
}

This would produce:

Circle Characteristics
Radius: 32.48
Area:   3314.22442654161
Press any key to continue . . .
 

Previous Copyright © 2006 FunctionX, Inc. Next