实例解析C++/CLI之代理与事件(下)
上一篇 /
下一篇 2007-02-07 09:29:00
/ 个人分类:读书笔记
System::Delegate
代理类型的定义,会隐式地创建一个对应的类(class)类型,并且所有的代理类型均从类库System::Delegate继承而来。要定义一个这样的类,唯一的方法就是通过delegate上下文关键字。代理类为隐式的sealed,因此它们不能被用作基类。另外,一个非代理类也不能从System::Delegate继承。
例6演示了几个Delegate函数的用法:
例6:
using namespace System; delegate void D(int x);
ref class Test { String^ objName; public: Test(String^ objName) { this->objName = objName; } void M(int i) { Console::WriteLine("Object {0}: {1}", objName, i); } };
void ProcessList(D^ del, int value, Object^ objToExclude);
int main() { /*1*/ Test^ t1 = gcnew Test("t1"); D^ cd1 = gcnew D(t1, &Test::M); /*2*/ Test^ t2 = gcnew Test("t2"); D^ cd2 = gcnew D(t2, &Test::M); /*3*/ Test^ t3 = gcnew Test("t3"); D^ cd3 = gcnew D(t3, &Test::M); /*4*/ D^ list = cd1 + cd2 + cd3 + cd2; /*5a*/ ProcessList(list, 100, nullptr); /*5b*/ ProcessList(list, 200, t1); /*5c*/ ProcessList(list, 300, t2); /*6a*/ D^ cd4 = cd1 + cd2; /*6b*/ D^ cd5 = (D^)cd4->Clone(); /*6c*/ ProcessList(cd4, 5, nullptr); /*6d*/ ProcessList(cd5, 6, nullptr); } void ProcessList(D^ del, int value, Object^ objToExclude) { /*7*/ if (del == nullptr) { return; } /*8*/ else if (objToExclude == nullptr) { del(value); } else { /*9*/ array<Delegate^>^ delegateList = del->GetInvocationList(); for each (Delegate^ d in delegateList) { /*10*/ if (d->Target != objToExclude) { /*11*/ ((D^)d)(value); } } } } |
实例函数Test::M与代理类型D相兼容,当调用时,这个函数只是识别出它调用的对象,并带有一个整数参数。
在标号1、2、3中,定义了三个Test类型的对象,并把它们各自与实例函数Test:M包装在单独的代理类型D中。接着,在标号4中,创建了一个四入口的调用列表。
倘若传递进来的调用列表不为空,ProcessList函数将调用在列表中除了特定对象以外的所有函数,例如,在标号5a中,没有排除任何入口,因此所有的函数都会被调用;在标号5b中,t1被排除在外,而标号5c中,与对象t2有关的两个入口都被排除了,结果输出如下:
Object t1: 100 Object t2: 100 Object t3: 100 Object t2: 100 Object t2: 200 Object t3: 200 Object t2: 200 Object t1: 300 Object t3: 300 |
在标号6b中,调用了Clone创建了代理cd4的一个副本,这个函数返回一个Object^,因此,要把它转换成D^类型。当原始及克隆的代理在标号6c、6d中调用时,结果输出如下:
Object t1: 5 Object t2: 5 Object t1: 6 Object t2: 6 |
关于函数ProcessList,如果参数中的代理实例为nullptr,即没有调用列表,那它将直接返回;如果排除的对象为nullptr,那么列表中所有的函数都将被调用;如果存在要排除的对象,就要像标号8中那样把调用列表当作代理数组取出,接着,在标号9中逐个排查不相符的入口,最后,在标号10中调用余下的这些函数。尽管在调用列表中每个入口都是Del类型,但GetInvocationList返回一个基类Delegate数组,所以在调用每个代理实例之前,需像标号10那样先转换成类型D。
事件
在C++/CLI中,事件是一种当某种重要事情发生时,为客户程序提供通知的机制。鼠标单击就是事件的一个典型例子,在事件发生之前,有关的客户程序必须先注册它们感兴趣的事件,如,当检测到鼠标单击时,这些程序就会接到通知。
通过添加或删除一个或多个感兴趣的事件,事件列表可在运行时增长或缩减,请看例7中Server类型的定义,在标号1中,Server类定义了代理类型NewMsgEventHandler(一般约定在用于事件处理时,代理类型添加EventHandler的后缀名),接着,在标号2中,定义了一个名为ProcessNewMsg的公共事件(event在此为一个上下文关键字)。一个事件必须有一个代理类型,实际上,像这样的一个事件已经是一个代理实例了,而且因为它被默认初始化为nullptr,所以它没有调用列表。
例7:
using namespace System;
public ref struct Server { /*1*/ delegate void NewMsgEventHandler(String^ msg); /*2*/ static event NewMsgEventHandler^ ProcessNewMsg; /*3*/ static void Broadcast(String^ msg) { if (ProcessNewMsg != nullptr) { ProcessNewMsg(msg); } } }; |
当通过一条消息调用时,函数Broadcast将调用包装在ProcessNewMsg调用列表中所有的函数。
Client类定义在例8中,一个Client的类型实例无论何时被创建,它都会通过向为Server::ProcessNewMsg维护的代理列表中添加一个实例函数(它关联到实例变量),来注册它所感兴趣的新Server消息,而这是通过 += 操作符来完成,如标号5中所示。只要这个入口一直保持在通知列表中,无论何时一个新消息送达Server,注册的函数都会被调用。
例8:
using namespace System; public ref class Client { String^ clientName; /*4*/ void ProcessNewMsg(String^ msg) { Console::WriteLine("Client {0} received message {1}", clientName, msg); } public: Client(String^ clientName) { this->clientName = clientName; /*5*/ Server::ProcessNewMsg += gcnew Server::NewMsgEventHandler(this, &Client::ProcessNewMsg); } /*6*/ ~Client() { Server::ProcessNewMsg -= gcnew Server::NewMsgEventHandler(this, &Client::ProcessNewMsg); } }; |
要从通知列表中移除一个入口,可使用 -= 操作符,如标号6定义的析构函数中那样。
例9:
using namespace System;
int main() { Server::Broadcast("Message 1"); Client^ c1 = gcnew Client("A"); Server::Broadcast("Message 2");
Client^ c2 = gcnew Client("B"); Server::Broadcast("Message 3");
Client^ c3 = gcnew Client("C"); Server::Broadcast("Message 4");
c1->~Client(); Server::Broadcast("Message 5");
c2->~Client(); Server::Broadcast("Message 6");
c3->~Client(); Server::Broadcast("Message 7"); } |
例9是主程序,一开始,没有注册任何函数,所以当发送第一个消息时,不会获得任何通知。然而,一旦构造了c1,通知列表就包含了此对象的一个入口,而接下来c2与c3的构造使这个列表增长到3个入口。在这些对象消失时(通过显式调用析构函数),入口数也相应地减少了,直到最后,一个也不剩,因此当最后一条消息发出时,没有任何对象在监听。以下是输出:
Client A received message Message 2 Client A received message Message 3 Client B received message Message 3 Client A received message Message 4 Client B received message Message 4 Client C received message Message 4 Client B received message Message 5 Client C received message Message 5 Client C received message Message 6 |
尽管3个对象均为同一类型,但这并不是必须的,只要定义的函数可与NewMsgEventHandler兼容,就能使用任意的类型。
上述例子中使用的事件只不过是微不足道的一个示例,另外要说明一点,与以前文章中说过的属性一样,此种类型的事件均以private属性自动备份,且自动生成添加(add)与移除(remove)存取程序,为自定义这些存取程序,就必须提供这些函数的定义,如例10中所示,名称add与remove在此为上下文关键字。
例10:
public ref struct Server { // ... static event NewMsgEventHandler^ ProcessNewMsg { void add(NewMsgEventHandler^ n) { /* ... */ } void remove(NewMsgEventHandler^ n) { /* ... */ } } // ... }; |
导入论坛
引用链接
收藏
分享给好友
管理
举报
TAG: