What Are Delegates?


The function pointer mechanism in C and C++ has been used by programmers for many years, and it’s a very useful way of implementing mechanisms such as event handlers. Unfortunately, function pointers are a C++ language feature, so they’re of no use in the .NET environment, where features need to be accessible from many languages. If you’re interested in knowing more about function pointers and how they work, see the following sidebar, “What Are Function Pointers?”

Delegates are the .NET equivalent of function pointers, and they can be created and used from any .NET language. They can be used by themselves, and they also form the basis for the .NET event mechanism discussed in the second part of this chapter.

start sidebar
What Are Function Pointers?

A normal pointer lets you access a variable through the address it contains. A function pointer lets you execute a function using the address of the routine. In exactly the same way that you can use a pointer to hold the addresses of different variables, you can use the same function pointer to invoke different functions. And in the same way that normal pointers must have a type associated with them (so that you can only point at doubles with a double*, for example), function pointers must have a function signature associated with them.

The following line of code shows how you declare a function pointer in C++:

long (*pf)(int, int);

The code declares a function pointer called pf, which can be used to invoke any function that takes two int parameters and returns a long. The following function prototype has the right signature:

long func1(int, int);

You can invoke the function indirectly like this:

pf = func1; // assign address of func1 to pf long l = pf(3,4); // invoke func1() through pf

Remember that in C++, the name of a function without any parentheses evaluates to its address, so the first line takes the address of the function and stores it in pf. The second line uses pf to invoke the function.

You can use a function pointer to invoke any function that matches its signature, and that’s what makes function pointers useful for event handling. You can define a function pointer to represent the event handler and then hook up the actual function to the pointer later.

end sidebar

What Do Delegates Do?

A delegate is a class that lets you invoke one or more methods that have a particular signature. Here’s a simple example to show when you might want to use a delegate.

Imagine that I want to be able to perform operations on numbers by passing a number into a function and getting a transformed value back, like this:

double d = 3.0; double result = square(d); result = cube(d); result = squareRoot(d); result = tenToThePowerOf(d);

In each case, I’m calling a function that has the same signature: one that takes a double and returns a double as its result.

With delegates, I can define a mechanism that will let me call any of those methods because they all have the same signature. Not only can I call any of the four methods above, but I can also define other methods and can call them through the delegate—provided that the signature matches. This makes it possible for one class or component to define a delegate, and for other classes to attach functions to the delegate and use it. You’ll see examples of this use of delegates later in the chapter when we cover events.

In this case, I want to use the delegate to call one method at a time, but it’s possible to attach more than one function to a delegate. All the functions will get called in order when the delegate is invoked. The .NET Framework defines the System::Delegate class as the base for delegates that call a single method, and System::MulticastDelegate as the base for delegates that can call more than one method. All delegates in managed C++ are multicast delegates.

Defining Delegates

This exercise uses the numerical operations example from the previous section to show you how to create and use a simple delegate in managed C++ code.

  1. Open Microsoft Visual Studio .NET if it isn’t already open, and create a new Visual C++ Console Application (.NET) project named Delegate.

  2. Open the Delegate.cpp source file, and add the definition of a delegate to the top of the file, immediately after the using namespace System; line.

    __delegate double NumericOp(double);

    The __delegate keyword is used to define a delegate. It might look as though this is a function prototype for a function named NumericOp, but it’s actually defining a delegate type that inherits from System::MulticastDelegate. This delegate, named NumericOp, can be bound to any function that takes one double as an argument and returns a double.

Implementing Delegates

Now that you have defined a delegate, you can write code to use it to call functions. One of the rules for using delegates is that you can only use a delegate to call functions that are members of managed C++ classes; you can’t use a delegate to call a global function or a function that’s a member of an unmanaged C++ class.

Calling Static Member Functions Using Delegates

Let’s start by looking at the simplest case: calling static member functions using a delegate.

  1. All the functions we want to call need to be static members of a class, so add a class to your source code file, above the _tmain function, that looks like this:

    __gc class Ops { public: static double square(double d) { return d*d; } };

    The class definition has to use __gc to make it a managed class, and it contains one public static method, which simply takes a number and returns its square.

  2. Create a delegate in the _tmain function of the program, as shown here:

    // Declare a delegate NumericOp* pOp = new NumericOp(0, &Ops::square);

    When you declared the delegate, you created a new reference type named NumericOp, so you can now create a NumericOp object. The constructor takes two arguments. The first one is a pointer to an object, used when calling non-static members, so in this example, it’s a null pointer. The second argument is the address of the function that is to be associated with the delegate, so you use the & operator to specify the address of Ops::square.

    The object pointed to by pOp is now set up so that it will call the square function when it is invoked, and it will take exactly the same arguments (and return the same type) as Ops::square.

    Note

    You can’t change the function that a delegate invokes once it’s been created. In this respect, delegates differ from C++ function pointers.

  3. Every delegate has an Invoke method that you use to call the function that has been bound to the delegate. Invoke will take the same arguments and return the same type as the function being called. Add the following lines to use pOp to call the square function:

    // Call the function through the delegate double result = pOp->Invoke(3.0); Console::WriteLine(S"Result is {0}", __box(result));
  4. You can now easily create another static member, create a delegate, and call the function. Test this out by adding a second static member to the Ops class named cube.

    static double cube(double d) { return d*d*d; }
  5. Create another delegate in the same way as the first, but this time, pass it the address of the cube function in the constructor.

    // Declare a second delegate NumericOp* pOp2 = new NumericOp(0, &Ops::cube);

    When you call Invoke on this delegate, it will call the cube function for you, as seen here:

    // Call the function through the delegate double result2 = pOp2->Invoke(3.0); Console::WriteLine(S"Result of cube() is {0}", __box(result2));

Calling Non-Static Member Functions Using Delegates

You can also call non-static member functions of classes by using delegates. By definition, a non-static member function has to be called on an object, so you need to tell the delegate the function it’s going to call and the object it’s going to use. You do so in the delegate’s constructor, like this:

// Declare a delegate bound to a non-static member MyDelegate* pDel = new MyDelegate(pMyObject, &MyClass::myFunction);

The constructor specifies the address of an object, pMyObject, and a member function belonging to the class to which pMyObject belongs. Calling Invoke on this delegate is equivalent to directly calling pMyObject->myFunction.

Using Multicast Delegates

We’ve seen how it’s possible to use a delegate to call a single function, but it’s also possible for a delegate to call more than one function with a single call to Invoke. A delegate that does so is called a multicast delegate and is derived from the System::MulticastDelegate class.

Note

All delegates that you create in managed C++ using the __delegate keyword are multicast delegates.

All delegate objects have an invocation list that holds the functions to be called. The invocation list for a normal delegate has one member. You manipulate the invocation lists for multicast delegates using the Combine and Remove methods.

If you look at the documentation for the Combine method, you’ll see that it takes two or more Delegate objects as its arguments. You don’t build up a multicast delegate by specifying more functions to add to its invocation list. Instead, a multicast delegate is built up by combining other delegates, which can, in turn, be single or multicast delegates themselves.

The following exercise shows you how to create and use a multicast delegate.

  1. Open Visual Studio .NET if it isn’t already open, and create a new Visual C++ Console Application (.NET) project named Multicast.

  2. Open the Multicast.cpp source file, and add the definition of a delegate to the top of the file, immediately after the using namespace System; line.

    __delegate void NotifyDelegate(int);

    This delegate, named NotifyDelegate, can be bound to any function that takes one int as an argument and returns void.

  3. You’re going to call two functions through the multicast delegate. Because all functions called by delegates have to be members of a managed class, define two classes at the start of your project, each of which contains a static member function.

    __gc class Client1 { public: static void NotifyFunction1(int n) { Console::WriteLine(S"Client1: got value {0}", __box(n)); } }; __gc class Client2 { public: static void NotifyFunction2(int n) { Console::WriteLine(S"Client2: got value {0}", __box(n)); } }; 

    These two classes are almost identical, both defining a single static member function that has the signature required by the delegate.

  4. You want to call the two static member functions through one delegate, but you can’t create a delegate to bind to two functions directly. Instead, you need to create two normal delegates (as you did in the previous exercise) and combine them into a multicast delegate. So, define two delegates in the _tmain function, each of which binds to one of the static methods.

    Console::WriteLine(S"Multicast Delegates"); // Declare two delegates NotifyDelegate *pDel1, *pDel2; // Bind them to the functions pDel1 = new NotifyDelegate(0, &Client1::NotifyFunction1); pDel2 = new NotifyDelegate(0, &Client2::NotifyFunction2);

    At this stage, you could use Invoke to use both of the delegates, just as you did in the previous exercise.

  5. Build a multicast delegate from pDel1 and pDel2 by using the
    Delegate class’s static Combine method, like this:

    // Combine the invocation lists of the two delegates NotifyDelegate *pDel3 = dynamic_cast<NotifyDelegate*>(Delegate::Combine(pDel1, pDel2));

    Combine is a static member of the Delegate class that takes pointers to two delegates and returns you a new delegate whose invocation list combines the entries from both delegates.

    You need to use a cast because the pointer returned from Combine is simply a Delegate* and it needs to be cast into a NotifyDelegate* before it can be used.

    Note

    NoteIn other .NET languages, such as Microsoft Visual Basic and Visual C#, the syntax is simpler, but multicast delegates are still being created using the same mechanism.

  6. The multicast delegate is now used in exactly the same way as the normal delegates, using the Invoke method:

    // Invoke the multicast delegate Console::WriteLine(S"Invoking pDel3"); pDel3->Invoke(3); 

    When you build and run the program, you should see two lines of output, as shown in the following figure.

    click to expand

    Note that the functions are called in the order in which the delegates are combined, so if you want to change the order, you’ll need to change the way you create the multicast.

  7. You can use this multicast delegate as the basis for making up another one.

    // Create a second multicast delegate and invoke it NotifyDelegate *pDel4 = dynamic_cast<NotifyDelegate*>(Delegate::Combine(pDel3, pDel3)); Console::WriteLine(S"Invoking pDel4"); pDel4->Invoke(3);

    In this case, you’re combining the invocation list of pDel3 twice, which means that you’ll get the output shown in the following figure when you invoke it.

    click to expand

  8. As the final part of this exercise, you can use the Remove method to remove an item from a multicast delegate’s invocation list. Here’s how:

    // Remove an item NotifyDelegate *pDel5 = dynamic_cast<NotifyDelegate*>(Delegate::Remove(pDel3, pDel1)); Console::WriteLine(S"Invoking pDel5"); pDel5->Invoke(3);

    The Remove function takes two arguments: the delegate to be operated on, and the item to remove. If the item to be removed exists in the invocation list of the first delegate, it’s removed. In this example, pDel1 is being removed from the invocation list of pDel3, leaving only pDel2. If you invoke this delegate, you’ll see in the following figure that you get only the output from pDel2.

    click to expand




Microsoft Visual C++  .NET(c) Step by Step
Microsoft Visual C++ .NET(c) Step by Step
ISBN: 735615675
EAN: N/A
Year: 2003
Pages: 208

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net