Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1

.pdf
Скачиваний:
70
Добавлен:
16.08.2013
Размер:
24.18 Mб
Скачать

178

C H A P T E R 4 A D V A N C E D C + + / C L I

With C++/CLI, it is now possible to remove this redundant coding by adding a finally block after the last catch block. The syntax for a finally block is the following:

finally

{

// Code to always be executed

}

All code within the finally block will always be executed after the completion of the try block or after the completion of the caught catch block.

As you can see in Listing 4-15, the finally block is run both at the successful completion of the try block and after the System::ApplicationException catch block is executed.

Listing 4-15. Finally.exe: The finally Block

using namespace System;

void main()

{

for (int i = 0; i < 3; i++)

{

Console::WriteLine("Start Loop"); try

{

if (i == 0)

{

Console::WriteLine("\tCounter = 0");

}

else if (i == 1)

{

throw gcnew ApplicationException("\t*Exception* Counter = 1");

}

else

{

Console::WriteLine("\tCounter greater than 1");

}

}

catch (ApplicationException ^e)

{

Console::WriteLine(e->Message);

}

finally

{

Console::WriteLine("\tDone every time");

}

Console::WriteLine("End Loop");

}

}

Figure 4-9 shows the results of this little program.

C H A P T E R 4 A D V A N C E D C + + / C L I

179

Figure 4-9. Results of Finally.exe

Delegates and Events

Delegates and events are completely new concepts to the traditional C++ developer. Truth be told, both provide the same functionality, allowing functions to be manipulated as reference handles. Because a handle can be assigned to more than one value in its lifetime, it is possible to have functions executed based on whichever function was last placed in the handle.

For those of you with a C++ background, you might notice that this object-oriented approach is very similar to function pointers. Where they differ is that delegates and events are ref classes and not pointers, and delegates and events only invoke global functions or member methods of ref classes.

You might be wondering, if they all do the same thing, why introduce the new concepts? Remember that a key aspect of .NET is language independence. Unfortunately, function pointers are strictly a C++ language feature and are not easily implemented in other languages, especially languages that have no pointers. Also, function pointers are far from easy to implement. Delegates and events were designed to overcome these problems.

Delegates

A delegate is a ref class that accepts and then invokes one or more methods that share the same signature from global functions or other classes that have methods with this same signature.

The .NET Framework supports two forms of delegates:

System::Delegate: A delegate that accepts and invokes only a single method.

System::MulticastDelegate: A delegate that accepts and invokes a chain of methods. A MulticastDelegate can perform something known as multicast chaining, which you can think of as a set of delegates linked together and then later, when called, executed in sequence.

C++/CLI only supports multicast delegates, but this really isn’t a problem because there’s nothing stopping a multicast delegate from accepting and invoking only one method.

The creating and implementing of delegates is a three-part process with an optional fourth part if multicast chaining is being implemented:

1.Create the delegate.

2.Create the global function(s) or member method(s) to be delegated.

3.Place the method on the delegate.

4.Combine or remove delegates from the multicast chain.

180

C H A P T E R 4 A D V A N C E D C + + / C L I

Creating a Delegate

The code involved in creating a delegate is extremely easy. In fact, it is just a method prototype prefixed with the keyword delegate. By convention, a delegate is suffixed with “delegate” but this is not essential, for example:

delegate void SayDelegate(String ^name);

What happens in the background during the compilation process is a lot more complex. This statement actually is converted to a class with a constructor to accept delegated methods and three member methods to invoke these methods. Figure 4-10 shows the effects of the resulting compilation by running the program ILDASM in Listing 4-16.

Figure 4-10. ILDASM snapshot of the generated delegate class

Creating a Method to Be Delegated

There is nothing special about creating a global function or member method for delegating. The only criteria are that it has the same signature as the delegate and that if it is a member method that it has public scope. The method can be a global function:

void SayHello(String ^name)

{

Console::Write("Hello there "); Console::WriteLine(name);

}

a static member method:

ref class Talkative

{

public:

static void SayHello(String ^name);

};

or an instance member method:

ref class Talkative

{

public:

void SayStuff(String ^name);

};

C H A P T E R 4 A D V A N C E D C + + / C L I

181

Placing a Method on the Delegate

This is the least obvious part of the delegating process. The reason is that you need to implement the auto-generated constructor of the delegate class. If you were not aware that a delegate was a class, then the syntax would appear quite confusing. But, because you are, it should be obvious that all you are doing is creating a new instance of the delegate class for each method that you want to delegate.

There are two constructors for a delegate. The first takes the address of the method as a parameter. This constructor is used when the method is a global function or a static member method:

delegate-name (address-of-method);

The other constructor is for instance member methods and takes two parameters, the handle to the instance of the class within which the member method can be found and fully referenced address of the method:

delegate-name (handle-of-object, address-of-method);

For example, here are delegations of a global function and a static and instance member methods:

// Global Function

SayDelegate ^say = gcnew SayDelegate(&SayHello);

// Static member functions

SayDelegate ^hello = gcnew SayDelegate(&Talkative::SayHi);

// Instance member functions

Talkative ^computer = gcnew Talkative();

SayDelegate ^stuff = gcnew SayDelegate(computer, &Talkative::SayStuff);

Combining and Removing Delegates from a Multicast Chain

These are the trickiest parts of the delegating process, which doesn’t say much. The reason they’re tricky is that they require the use of two auto-generated methods or two overloaded operators:

Combine() method or + operator

Remove() method or - operator

These methods or operators make sense as you are combining (or adding) and removing (or subtracting) methods from the delegate class.

The syntax for both combining and removing is exactly the same, except for, of course, the operator of the method being called:

// create initial delegate

SayDelegate say = gcnew SayDelegate(&SayHello);

// add Static member function

say = say + gcnew SayDelegate(&Talkative::SayHi); // -or-

say += gcnew SayDelegate(&Talkative::SayHiThere);

// remove delegate

say = say - gcnew SayDelegate(&Talkative::SayHi); // -or-

say -= gcnew SayDelegate(&Talkative::SayHiThere);

182

C H A P T E R 4 A D V A N C E D C + + / C L I

The + operator takes the two delegates, chains them together, and then places them on a delegate. The - operator does the opposite of the + operator. It removes the specified delegate from the delegate multicast chain and then places the new chain on a delegate.

I never use the auto-generated methods, because the overloaded operators are so much easier to code. But here they are if you want to use them:

SayDelegate ^say =

(SayDelegate^)(Delegate::Combine(say, gcnew SayDelegate(&SayHello)));

SayDelegate ^say =

(SayDelegate^)(Delegate::Remove(say, gcnew SayDelegate(&SayHello)));

See what I mean? It’s a lot more coding, and a type cast is required.

Invoking a Delegate

The process of invoking a delegate is quite simple, but not obvious if you are not aware that a delegate is a class. All you have to do is either call the auto-generated member method Invoke or call the class itself as if it were a method with the parameter list that you specified when you created the delegate:

say->Invoke("Mr Fraser"); // -or-

say("Stephen");

There is no difference in the syntax, whether you invoke one method or a whole chain of methods. The Invoke method simply starts at the top of the chain and executes methods until it reaches the end. If there is only one method, then it only executes that one method.

Listing 4-16 is a complete example of creating, adding, removing, and invoking delegates. The example simply creates a delegate, adds four different types of methods to the delegate chain, and invokes the delegate. Then it removes two of the methods from the delegate chain and invokes the delegate again, but this time the delegate contains only two methods.

Listing 4-16. Delegates.exe: Programming Delegates

using namespace System;

///<summary>A Delegate that talks a lot</summary> delegate void SayDelegate(String ^name);

///<summary>A friendly function</summary>

void SayHello(String ^name)

{

Console::Write("Hello there "); Console::WriteLine(name);

}

/// <summary>A talkative class</summary> ref class Talkative

{

public:

static void SayHi(String ^name); void SayStuff(String ^name); void SayBye(String ^name);

};

C H A P T E R 4 A D V A N C E D C + + / C L I

183

void Talkative::SayHi(System::String ^name)

{

Console::Write("Hi there "); Console::WriteLine(name);

}

void Talkative::SayStuff(System::String ^name)

{

Console::Write("Nice weather we are having. Right, "); Console::Write(name);

Console::WriteLine("?");

}

void Talkative::SayBye(System::String ^name)

{

Console::Write("Good-bye "); Console::WriteLine(name);

}

/// <summary>Delegates in action</summary> void main()

{

SayDelegate^ say;

// Global Function

say = gcnew SayDelegate(&SayHello);

// add Static member function

say += gcnew SayDelegate(&Talkative::SayHi); Talkative ^computer = gcnew Talkative();

// add instance member functions

say = say + gcnew SayDelegate(computer, &Talkative::SayStuff); say += gcnew SayDelegate(computer, &Talkative::SayBye);

// invoke delegate say->Invoke("Stephen");

Console::WriteLine("-------------------------------");

// remove a couple of methods

say = say - gcnew SayDelegate(&Talkative::SayHi);

say -= gcnew SayDelegate(computer, &Talkative::SayBye);

// invoke delegate again with two fewer methods say("Stephen");

}

Figure 4-11 shows the results of this little program.

184

C H A P T E R 4 A D V A N C E D C + + / C L I

Figure 4-11. Results of Delegates.exe

Events

An event is a specific implementation of delegates. You’ll see it used quite extensively when I describe Windows Forms in Chapters 9 and 10. For now, you can explore what events are and how they work without worrying about the .NET Framework event model.

In simple terms, events allow one class to trigger the execution of methods found in other classes without knowing anything about these classes or even from which classes it is invoking the method. This allows a class to execute methods and not have to worry about how, or even if, they are implemented. Because events are implemented using multicast delegates, it is possible for a single class to call a chain of methods from multiple classes.

There are always at least two classes involved with events. The first is the source of the event. This class generates an event and then waits for some other class, which has delegated a method to handle the event, to process it. If there are no delegated methods to process the event, then the event is lost. The second and subsequent classes, as was hinted previously, receive the event by delegating methods to handle the event. Truthfully, only one class is needed to handle an event, given that the class that created the event could also delegate a method to process the event. But why would you want to do this, when a direct call to the method could be used, thus avoiding the event altogether? And it would be much more efficient.

Building an Event Source Class

Before you create an event source class, you need to define a delegate class on which the event will process. The delegate syntax is the same as was covered previously. In fact, there is no difference between a standard delegate and one that handles events. To differentiate between these two types of delegates, by convention delegates that handle events have a suffix of “Handler”:

delegate void SayHandler(String ^name);

Once you have the delegate defined, you can then create an event source class. There are basically two pieces that you will find in all event source classes: the event and an event trigger method. Like delegates, events are easy to code but do a little magic in the background. To create an event, include within a ref class in a public scope area a delegate class declaration prefixed by the keyword event:

ref class EventSource

{

public:

event SayHandler^ OnSay; //...

};

C H A P T E R 4 A D V A N C E D C + + / C L I

185

Simple enough, but when the compiler encounters this, it is converted into three member methods:

add_<delegate-name>: A public member method that calls the Delegate::Combine method to add delegated receiver class methods. To simplify the syntax, use the overloaded += operator instead of calling add_<delegate-name> directly.

remove_<delegate-name>: A public member method that calls the Delegate::Remove method to remove delegated receiver class methods. To simplify the syntax, use the overloaded -= operator instead of calling remove_<delegate-name> directly.

raise_<delegate-name>: A protected member method that calls the Delegate::Invoke method to call all delegated receiver class methods. This method is protected so that client classes cannot call it. It can only be called through a managed internal process.

Figure 4-12 is an ILDASM snapshot that shows the methods that were created by the event keyword within the event source class in Listing 4-17.

Figure 4-12. ILDASM snapshot of the generated event member methods

Finally, now that you have an event, you need a way to trigger it. The triggering event can be almost anything. In Web Forms, the triggering event will be handled by things such as mouse clicks and key presses. In this case, you will simply call the delegate directly:

ref class EventSource

{

public:

event SayHandler^ OnSay;

void Say(String ^name)

{

OnSay(name);

}

};

Notice that I don’t have to make sure that the event is not a nullptr. With C++/CLI, the event has a default value that does nothing, so we don’t have to make the check. In fact, if you check to see if the event is unassigned by comparing the event to nullptr, you get the compile-time error C3918. This code generates the following error:

186

C H A P T E R 4 A D V A N C E D C + + / C L I

void Say(String ^name)

{

if (OnSay != nullptr) // Error C3918 is generated OnSay(name);

}

Caution If you are converting Managed Extensions for C++ code to C++/CLI code, you will have to check that you don’t compare an event or delegate to a nullptr, because this was how the validation of the event or delegate was originally handled.

Building Event Receiver Class(es)

One or more classes can process an event. The process for delegating a member class to an event is identical for each class. Other than the simplified syntax, you will find that event handling and delegate processing are the same. First, you create the member method to delegate. Then you combine it on the event handler.

The first thing you need to do is create a public ref class member method to be delegated to the event handler. Nothing is new here:

ref class EventReceiver

{

public: //...

void SayBye(String ^name)

{

Console::Write("Good-bye "); Console::WriteLine(name);

}

};

Then, to combine this method on the event handler, the event receiver class must know with which event source class it will be associated. The easiest way to do this is to pass it through the constructor. To avoid a null handle error, check to make sure that the handle was passed. I could make more thorough validations, such as verifying the type of class, but this is enough to convey the idea.

Now that you have the event source class and a member method to place, it is simply a matter of creating a new instance of a delegate of the event’s delegate type and combining it. Or, in this case, using the operator += to combine the new delegate to the event within the source event class:

ref class EventReceiver

{

EventSource ^source; public:

EventReceiver(EventSource ^src)

{

if (src == nullptr)

throw gcnew ArgumentNullException("Must pass an Event Source"); source = src;

source->OnSay += gcnew SayHandler(this, &EventReceiver::SayBye);

}

//...

};

C H A P T E R 4 A D V A N C E D C + + / C L I

187

What if you have a delegated method that you no longer want handled by the event? You would remove it just as you would a standard delegate. The only difference is that you can now use the -= operator:

source->OnSay -= gcnew SayHandler(this, &EventReceiver::SayStuff);

Implementing the Event

You now have both a source and a receiver class. All you need to do is create instances of each and then call the event trigger method.

void main()

 

{

 

EventSource ^source

= gcnew EventSource();

EventReceiver ^receiver = gcnew EventReceiver(source);

source->Say("Mr Fraser");

}

Listing 4-17 shows all of the code needed to handle an event. This time, the event source class has two event receiver classes. The event is triggered twice. The first time, all delegates are combined and executed. The second time, one of the delegates is removed. You might notice that the member methods are very familiar.

Listing 4-17. Events.exe: Programming Events

using namespace System;

delegate void SayHandler(String ^name);

ref class EventSource

{

public:

event SayHandler^ OnSay;

void Say(String ^name)

{

OnSay(name);

}

};

ref class EventReceiver1

{

EventSource ^source; public:

EventReceiver1(EventSource ^src)

{

if (src == nullptr)

throw gcnew ArgumentNullException("Must pass an Event Source"); source = src;