知识大全 实例解析C++/CLI之代理与事件
Posted 函数
篇首语:讨厌自己明明不甘平凡,却又不好好努力。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 实例解析C++/CLI之代理与事件相关的知识,希望对你有一定的参考价值。
实例解析C++/CLI之代理与事件 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!
在C++/CLI中 代理是对函数进行包装的对象 而事件是一种为客户程序提供通知的类机制 在前几篇文章中 已经多次演示了如果让一个句柄在不同的时间 被引用至不同的对象 从而以更抽象的方法来解决程序中的问题 但是 也能使用代理通过函数来达到同样的效果 代理是包装了函数的一个对象 且对实例函数而言 也能通过特定的实例 与这些函数发生联系 一旦一个代理包装了一个或多个函数 你就能通过代理来调用这些函数 而无须事先了解包装了哪些函数 请看例 中的代码 在标号 中 定义一个代理类型Del 由于使用了上下文关键字delegate 所以有点像函数的声明 但与函数声明不同的是 此处声明的是一个代理类型Del的实例 其可包装进任意接受一个int类型作为参数并返回一个int值类型的函数(任意有效的参数列表及返回类型组合都是允许的) 一旦定义了某种代理类型 它只能被用于包装具有同样类型的函数 代理类型可被定义在源文件中或命名空间的范围内 也能定义在类中 并可有public或private访问控制属性 例 using namespace System;ref struct A static int Square(int i) return i * i; ;ref struct B int Cube(int i) return i * i * i; ;/* */delegate int Del(int value);int main() /* */ Del^ d = gcnew Del(&A::Square); /* */ Console::WriteLine( d( ) result = d( )); /* */ B^ b = gcnew B; /* */ d = gcnew Del(b &B::Cube); /* */ Console::WriteLine( d( ) result = d( )); 静态函数A::Square与实例函数B::Cube对Del来说 都具有相同的参数类型及返回类型 因此它们能被包装进同类型的代理中 注意 即使两个函数均为public 当考虑它们与Del的兼容性时 它们的可访问性也是不相关的 这样的函数也能被定义在相同或不同的类中 主要由程序员来选择 一旦定义了某种代理类型 就可创建此类型实例的句柄 并进行初始化或赋值操作 如标号 中所示的静态函数A::Square 及标号 中所示的实例函数B::Cube (此处只是出于演示的目的 否则把Cube做成实例函数没有任何好处 ) 创建一个代理实例涉及到调用一个构造函数 如果是在包装一个静态函数 只需传递进一个指向成员函数的指针 而对实例函数而言 必须传递两个参数 一个实例的句柄及指向实例成员函数的指针 在初始化代理实例之后 就能间接地调用它们包装的函数了 用法与直接调用原函数一样 只不过现在用的是代理实例名 如标号 与 由包装函数返回的值也是像直接调用函数时那样获得 如果一个代理实例的值为nullptr 此时再试图调用被包装的函数 会导致System::NullReferenceException类型异常 以下是输出 d( ) result = d( ) result = 传递与返回代理 有时 把包装好的函数传递给另一个函数 会非常有用 接受一方的函数并不知道会传递过来哪个函数 并且它也无须关心 只需简单地通过包装好的代理 间接调用此函数就行了 下面以集合中元素排序来说明 大多数时候 集合中元素排序所依据的规则 只在对某对元素进行比较的方法上存在区别 如果在运行时提供进行比较的函数 一个排序过程就能用相应定义的比较函数排出任意的顺序 请看例 例 using namespace System;ref struct StrCompare static int CompareExact(String^ s String^ s ) Console::WriteLine( Comparing and using CompareExact s s ); // return ; static int CompareIgnoreCase(String^ s String^ s ) Console::WriteLine( Comparing and using CompareIgnoreCase s s ); // return ; ;delegate int Compare(String^ s String^ s );/* */Compare^ FindComparisonMethod() // void Sort(Compare^ pare) int result; /* */ result = pare( Hello Hello ); /* */ result = pare( Hello HELLO ); /* */ result = pare( Hello Hell );int main() /* */ Sort(gcnew Compare(&StrCompare::CompareIgnoreCase)); /* */ Sort(FindComparisonMethod()); /* */ FindComparisonMethod()( Red RED );Compare代理类型可对任意接受两个String^参数并返回一个int结果的函数进行包装 在此 有两个函数为StrCompare::CompareExact和StrCompare::CompareIgnoreCase 在标号 中 创建了一个Compare代理类型的实例 用它来包装StrCompare::CompareIgnoreCase 并把此代理句柄传递给Sort函数 其将会利用比较函数进一步进行处理 正如大家所看到的 Sort可接受一个代理类型的参数 而此参数可像其他函数参数一样 可为传值 传址 传引用 在标号 中 调用了FindComparisonMethod函数 其返回一个Del代理类型 接着在标号 及 中调用了包装过的函数 此处要重点说一下标号 首先 FindComparisonMethod函数是被调用来获取代理实例 其常用于调用底层函数 其次 这两个函数的调用操作符都有同等的优先级 所以它们从左至右调用 FindComparisonMethod函数中也用了一些逻辑用于确定到底需要包装哪个函数 此处就未作详细说明了
代理类型的兼容性 一个代理类型只与它自身相兼容 与其他任何代理类型都不兼容 即使其他类型的包装函数均为同一类型 请看例 非常明显 代理类型D 与函数A::M 与A::M 兼容 代理类型D 也与这些函数兼容 然而 这两个代理类型在标号 中并不能互换使用 例
delegate void D ();delegate void D ();public struct A static void M () /* */ static void M () /* */ ;void X(D ^ m) /* */ void Y(D ^ n) /* */ int main() D ^ d ; /* */ d = gcnew D (&A::M ); //兼容 /* */ d = gcnew D (&A::M ); //兼容 D ^ d ; /* */ d = gcnew D (&A::M ); //兼容 /* */ d = gcnew D (&A::M ); //兼容 /* */ d = d ; //不兼容 /* */ d = d ; //不兼容 /* */ X(d ); //兼容 /* */ X(d ); //不兼容 /* */ Y(d ); //不兼容 /* */ Y(d ); //兼容
代理类型的合并 一个代理实例实际上能包装多个函数 在这种情况下 被包装的函数集被维护在一个调用列表中 当合并两个代理实例时 它们的调用列表也以指定的顺序连接起来 并产生一个新的列表 而现有的两个列表并没有发生改变 当从调用列表中移除一个或多个函数时 也会产生一个新的列表 且原始列表不会发生变化 请看例 中的代码 每个函数调用后的输出都写在相应函数后 例
using namespace System;delegate void D(int x);ref struct Actions static void F (int i) Console::WriteLine( Actions::F : i); static void F (int i) Console::WriteLine( Actions::F : i); void F (int i) Console::WriteLine( instance of Actions::F : i); ;int main() /* */ D^ cd = gcnew D(&Actions::F ); //包含F 的调用列表 cd ( ); Actions::F : /* */ D^ cd = gcnew D(&Actions::F ); //包含F 的调用列表 cd ( ); Actions::F : /* */ D^ cd = cd + cd ; //包含F + F 的调用列表 cd ( ); Actions::F : Actions::F : /* */ cd += cd ; //包含F + F + F 的调用列表 cd ( ); Actions::F : Actions::F : Actions::F : Actions^ t = gcnew Actions(); D^ cd = gcnew D(t &Actions::F ); /* */ cd += cd ; //包含F + F + F + t >F 的调用列表 cd ( ); Actions::F : Actions::F : Actions::F : instance of Actions::F : /* */ cd = cd ; //移除最右边的F cd ( ); //调用F F t >F Actions::F : Actions::F : instance of Actions::F : /* */ cd = cd ; //移除t >F cd ( ); //调用F F /* */ cd = cd ; //移除F cd ( ); //调用F /* */ cd = cd ; //移除F 调用列表现在为空 /* */Console::WriteLine( cd = (cd == nullptr ? null : not null ));Actions::F : Actions::F : Actions::F : cd = null
代理可通过 + 和 += 操作符来合并 如标号 中所示 两个单入口列表会连接成一个新的双入口列表 以先左操作数 后右操作数的顺序 新的列表被cd 引用 而现有的两个列表并未改变 在此要注意的是 不能合并不同类型的代理 正如在标号 中所见 同一个函数可在一个调用列表中包装多次 而在标号 中 也说明了一个调用列表能同时包含类与实例函数 代理可通过 或 = 操作符移除 如标号 中所示 当同一个函数在调用列表中出现多次时 一个对它的移除请求会导致最右边的项被移除 在标号 中 这产生了一个新的三入口列表 其被cd 引用 且前一个列表保持不变(因为先前被cd 引用的列表现在引用计数为零 所以会被垃圾回收) 当一个调用列表中的最后一项被移除时 代理将为nullptr值 此处没有空调用列表的概念 因为 根本就没有列表了 例 中演示了另一个代理合并与移除的例子 正如标号 a与 b中所示 两个多入口调用列表是以先左操作数 后右操作数的顺序连接的 如果想移除一个多入口列表 只有当此列表为整个列表中严格连续的子集时 操作才会成功 例如 在标号 b中 你可以移除F 和F 因为它们是相邻的 对标号 b中的两个F 及标号 b中的F F 来说 道理也是一样的 但是 在标号 b中 列表中有两个连续的F 所以操作失败 而结果列表则是最开始的列表 它包含有 个入口 例
using namespace System;delegate void D(int x);void F (int i) Console::WriteLine( F : i); void F (int i) Console::WriteLine( F : i); int main() D^ cd = gcnew D(&F ); D^ cd = gcnew D(&F ); /* */ D^ list = cd + cd ; // F + F /* */ D^ list = cd + cd ; // F + F D^ cd = nullptr; /* a*/ cd = list + list ; // F + F + F + F cd ( ); /* b*/ cd = list + list ; // F + F + F + F cd ( ); /* a*/ cd = list + list ; // F + F + F + F /* b*/ cd = cd + cd ; // F + F cd ( ); /* a*/ cd = list + list ; // F + F + F + F /* b*/ cd = cd + cd ; // F + F cd ( ); /* a*/ cd = list + list ; // F + F + F + F /* b*/ cd = cd + cd ; // F + F cd ( ); /* a*/ cd = list + list ; // F + F + F + F /* b*/ cd = cd + cd ; // F + F + F + F cd ( );
System::Delegate 代理类型的定义 会隐式地创建一个对应的类(class)类型 并且所有的代理类型均从类库System::Delegate继承而来 要定义一个这样的类 唯一的方法就是通过delegate上下文关键字 代理类为隐式的sealed 因此它们不能被用作基类 另外 一个非代理类也不能从System::Delegate继承 例 演示了几个Delegate函数的用法 例
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 : objName i); ;void ProcessList(D^ del int value Object^ objToExclude);int main() /* */ Test^ t = gcnew Test( t ); D^ cd = gcnew D(t &Test::M); /* */ Test^ t = gcnew Test( t ); D^ cd = gcnew D(t &Test::M); /* */ Test^ t = gcnew Test( t ); D^ cd = gcnew D(t &Test::M); /* */ D^ list = cd + cd + cd + cd ; /* a*/ ProcessList(list nullptr); /* b*/ ProcessList(list t ); /* c*/ ProcessList(list t ); /* a*/ D^ cd = cd + cd ; /* b*/ D^ cd = (D^)cd >Clone(); /* c*/ ProcessList(cd nullptr); /* d*/ ProcessList(cd nullptr);void ProcessList(D^ del int value Object^ objToExclude) /* */ if (del == nullptr) return; /* */ else if (objToExclude == nullptr) del(value); else /* */ array<Delegate^>^ delegateList = del >GetInvocationList(); for each (Delegate^ d in delegateList) /* */ if (d >Target != objToExclude) /* */ ((D^)d)(value);
实例函数Test::M与代理类型D相兼容 当调用时 这个函数只是识别出它调用的对象 并带有一个整数参数 在标号 中 定义了三个Test类型的对象 并把它们各自与实例函数Test:M包装在单独的代理类型D中 接着 在标号 中 创建了一个四入口的调用列表 倘若传递进来的调用列表不为空 ProcessList函数将调用在列表中除了特定对象以外的所有函数 例如 在标号 a中 没有排除任何入口 因此所有的函数都会被调用 在标号 b中 t 被排除在外 而标号 c中 与对象t 有关的两个入口都被排除了 结果输出如下
Object t : Object t : Object t : Object t : Object t : Object t : Object t : Object t : Object t :
在标号 b中 调用了Clone创建了代理cd 的一个副本 这个函数返回一个Object^ 因此 要把它转换成D^类型 当原始及克隆的代理在标号 c d中调用时 结果输出如下
Object t : Object t : Object t : Object t :
关于函数ProcessList 如果参数中的代理实例为nullptr 即没有调用列表 那它将直接返回 如果排除的对象为nullptr 那么列表中所有的函数都将被调用 如果存在要排除的对象 就要像标号 中那样把调用列表当作代理数组取出 接着 在标号 中逐个排查不相符的入口 最后 在标号 中调用余下的这些函数 尽管在调用列表中每个入口都是Del类型 但GetInvocationList返回一个基类Delegate数组 所以在调用每个代理实例之前 需像标号 那样先转换成类型D
事件 在C++/CLI中 事件是一种当某种重要事情发生时 为客户程序提供通知的机制 鼠标单击就是事件的一个典型例子 在事件发生之前 有关的客户程序必须先注册它们感兴趣的事件 如 当检测到鼠标单击时 这些程序就会接到通知 通过添加或删除一个或多个感兴趣的事件 事件列表可在运行时增长或缩减 请看例 中Server类型的定义 在标号 中 Server类定义了代理类型NewMsgEventHandler(一般约定在用于事件处理时 代理类型添加EventHandler的后缀名) 接着 在标号 中 定义了一个名为ProcessNewMsg的公共事件(event在此为一个上下文关键字) 一个事件必须有一个代理类型 实际上 像这样的一个事件已经是一个代理实例了 而且因为它被默认初始化为nullptr 所以它没有调用列表 例
using namespace System;public ref struct Server /* */ delegate void NewMsgEventHandler(String^ msg); /* */ static event NewMsgEventHandler^ ProcessNewMsg; /* */ static void Broadcast(String^ msg) if (ProcessNewMsg != nullptr) ProcessNewMsg(msg); ;
当通过一条消息调用时 函数Broadcast将调用包装在ProcessNewMsg调用列表中所有的函数 Client类定义在例 中 一个Client的类型实例无论何时被创建 它都会通过向为Server::ProcessNewMsg维护的代理列表中添加一个实例函数(它关联到实例变量) 来注册它所感兴趣的新Server消息 而这是通过 += 操作符来完成 如标号 中所示 只要这个入口一直保持在通知列表中 无论何时一个新消息送达Server 注册的函数都会被调用 例
using namespace System;public ref class Client String^ clientName; /* */ void ProcessNewMsg(String^ msg) Console::WriteLine( Client received message clientName msg); public: Client(String^ clientName) this >clientName = clientName; /* */ Server::ProcessNewMsg += gcnew Server::NewMsgEventHandler(this &Client::ProcessNewMsg); /* */ ~Client() Server::ProcessNewMsg = gcnew Server::NewMsgEventHandler(this &Client::ProcessNewMsg); ;
要从通知列表中移除一个入口 可使用 = 操作符 如标号 定义的析构函数中那样 例
using namespace System;int main() Server::Broadcast( Message ); Client^ c = gcnew Client( A ); Server::Broadcast( Message ); Client^ c = gcnew Client( B ); Server::Broadcast( Message ); Client^ c = gcnew Client( C ); Server::Broadcast( Message ); c >~Client(); Server::Broadcast( Message ); c >~Client(); Server::Broadcast( Message ); c >~Client(); Server::Broadcast( Message );
例 是主程序 一开始 没有注册任何函数 所以当发送第一个消息时 不会获得任何通知 然而 一旦构造了c 通知列表就包含了此对象的一个入口 而接下来c 与c 的构造使这个列表增长到 个入口 在这些对象消失时(通过显式调用析构函数) 入口数也相应地减少了 直到最后 一个也不剩 因此当最后一条消息发出时 没有任何对象在监听 以下是输出
Client A received message Message Client A received message Message Client B received message Message Client A received message Message Client B received message Message Client C received message Message Client B received message Message Client C received message Message Client C received message Message
尽管 个对象均为同一类型 但这并不是必须的 只要定义的函数可与NewMsgEventHandler兼容 就能使用任意的类型 上述例子中使用的事件只不过是微不足道的一个示例 另外要说明一点 与以前文章中说过的属性一样 此种类型的事件均以private属性自动备份 且自动生成添加(add)与移除(remove)存取程序 为自定义这些存取程序 就必须提供这些函数的定义 如例 中所示 名称add与remove在此为上下文关键字 例
cha138/Article/program/net/201311/11477相关参考
Spring总结实例之消息与事件 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! Spring的消
张某委托孙某代理自己经营商店,下列事件中可导致此委托代理终止的是
张某委托孙某代理自己经营商店,下列事件中可导致此委托代理终止的是_____。A、张某因病死亡B、孙某因病死亡C、张某患了精神疾病D、孙某因小腿骨折住院治疗答案:B。解析:《民法通则》第69条规定,有下
下列事件与相关人物对应错误的是:_____A、三藩之乱:吴三桂,康熙B、七国之乱:吴王刘濞、汉景帝C、八王之乱:苻坚、谢玄D、安史之乱:郭子仪、史思明答案:C解析:C。八王之乱是西晋年间司马氏同姓王之
C#TextBox事件实现实例详解 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! C#TextB
C++/CLI基本数据类型探索 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! C++/CLI所支
下一代C++:C++/CLI语言的属性探索 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! 前言
法律事实有法律事件和法律行为之分,下列属于法律事件的是_____。A、人的出生B、行政命令C、地震D、离婚答案:AC解析:法律事件是法律规范规定的、不以当事人的意志为转移而引起法律关系形成、变更或消灭
C++/CLI中有效使用非托管并列缓存 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!VisualS
超越C++下一代C++—C++/CLI简介 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!一绪论
在宋朝时期,发生了:①靖康之乱;②杯酒释兵权;③金灭辽;④王安石变法。上述历史事件出现的先后顺序是
在宋朝时期,发生了:①靖康之乱;②杯酒释兵权;③金灭辽;④王安石变法。上述历史事件出现的先后顺序是_____。A、①③②④B、②①④③C、②④③①D、②③④①答案:C解析:1127年,金统治者俘获宋徽