9.9.7 委托和事件(1)
事件是类的成员,这种成员使对象能够在特定事件发生时发出信号,而为某个事件发出信号的过程涉及委托。鼠标单击是典型的事件示例,而引起鼠标单击事件的对象将通过调用一个或多个负责处理该事件的函数,发出该事件已经发生的信号。下面我们首先看一下委托,稍后再来讨论事件。
委托的思想非常简单:委托是能够封装一个或多个指针的对象,这些指针指向具有特定形参列表和返回类型的函数。因此,C++(www.cppentry.com)/CLI中的委托提供了与本地C++(www.cppentry.com)中的函数指针类似的功能性。然而,虽然委托的思想很简单,但创建和使用委托的细节可能会使人迷惑,因此现在请集中注意力。
1. 声明委托
委托的声明看起来就像是前面添加了delegate关键字的函数原型,但事实上定义了两件事情:委托对象的引用类型名以及可能与该委托有关的函数的形参列表和返回类型。委托的引用类型以System::Delegate作为基类,因此委托类型总是要继承该基类的成员。虽然委托的声明看来像是在前面添加了delegate关键字的函数原型,但实际上既定义了该委托的引用类型,又定义了可能与该委托有关的函数的签名。下面是一个委托声明的示例:
- public delegate void Handler(int value); // Delegate declaration
该语句将委托的引用类型定义为Handler,这里的Handler类型是从System::Delegate派生的。类型为Handler的对象可以包含拥有一个int类型形参、返回类型为void的一个或多个函数的指针。委托指向的函数可以是实例函数,也可以是静态函数。
2. 创建委托
定义了委托类型之后,我们就可以创建该类型的委托对象。委托的构造函数有两种选择:一种是接受单个实参,另一种是接受两个实参。
接受单个实参的委托构造函数的实参,必须是具有委托声明中指定的返回类型和形参列表的静态类函数成员或全局函数。假设我们像下面这样定义了一个名为HandlerClass的类:
- public ref class HandlerClass
- {
- public:
- static void Fun1(int m)
- { Console::WriteLine(L"Function1 called with value {0}", m); }
-
- static void Fun2(int m)
- { Console::WriteLine(L"Function2 called with value {0}", m); }
-
- void Fun3(int m)
- { Console::WriteLine(L"Function3 called with value {0}", m+value); }
-
- void Fun4(int m)
- { Console::WriteLine(L"Function3 called with value {0}", m+value); }
-
- HandlerClass():value(1){}
-
- HandlerClass(int m):value(m){}
- protected:
- int value;
- };
该类有4个形参类型为int、返回类型为void的函数。其中两个是静态函数,两个是实例函数。另外还有两个构造函数,其中一个是无参数的构造函数。这个类除了产生输出以外没有做更多的事情,我们根据输出可以确定被调用的是哪个函数,当实例函数被调用时还能确定当前对象是什么。
我们可以像下面这样创建一个Handler委托:
- Handler^ handler = gcnew Handler(HandlerClass::Fun1); // Delegate object
对象handler包含HandlerClass类中静态函数Fun1的地址。如果我们调用这个委托,则HandlerClass::Fun1()函数就将被调用,其实参与我们在委托调用中传递的实参相同。我们可以像下面这样编写委托调用:
- handler->Invoke(90);
该语句将调用所有在委托handler的调用列表中的函数。本例中只有一个函数在调用列表中-- 即HandlerClass::Fun1(),因此输出如下:
- Function1 called with value 90
我们也可以用下面的语句调用委托:
- handler(90);
这是前面显式调用Invoke()函数的那条语句的简写形式,也是常用的委托调用形式。
为了能够将两个委托的调用列表组合成一个新的委托对象,委托类型重载了+运算符。例如,我们可以用下面这条语句直观地修改handler委托的调用列表:
- handler += gcnew Handler(HandlerClass::Fun2);
handler变量现在将引用一个其调用列表包含Fun1和Fun2两个函数的委托对象。然而,这是个新的委托对象。某个委托的调用列表是不能被修改的,因此+运算符的工作方式类似于处理String对象的方式-- 即总是创建一个新对象。我们可以用下面这条语句再次调用该委托:
- handler(80);
现在的输出如下:
- Function1 called with value 80
- Function2 called with value 80
这次调用了调用列表中包含的两个函数,调用顺序与它们被添加到委托对象的顺序相同。
通过使用-运算符,我们还可以从委托的调用列表中有效地删除某一项:
- handler -= gcnew Handler(HandlerClass::Fun1);
该语句将新建一个调用列表中仅包含HandlerClass::Fun2()的委托对象,因为其作用是从handler的调用列表中删除右边的调用列表(HandlerClass::Fun1)包含的函数,并创建一个新的对象指向剩下的函数。
注意:
委托的调用列表必须至少包含一个函数指针。如果我们使用减法运算符删除所有函数指针,则结果将是nullptr。
当我们使用有两个形参的委托构造函数时,第一个实参是CLR堆上某个对象的引用,第二个实参是该对象所属的类型中某个实例函数的地址。因此,该构造函数创建的委托将包含一个由第二个实参指定的实例函数的指针,以便供第一个实参指定的对象使用。下面是创建这种委托的方法:
- HandlerClass^ obj = gcnew HandlerClass;
- Handler^ handler2 = gcnew Handler (obj, &HandlerClass::Fun3);