实例解析C++/CLI之代理与事件(上)
上一篇 / 下一篇 2007-02-07 09:28:00 / 个人分类:读书笔记
在C++/CLI中,代理是对函数进行包装的对象;而事件是一种为客户程序提供通知的类机制。
在前几篇文章中,已经多次演示了如果让一个句柄在不同的时间,被引用至不同的对象,从而以更抽象的方法来解决程序中的问题,但是,也能使用代理通过函数来达到同样的效果;代理是包装了函数的一个对象,且对实例函数而言,也能通过特定的实例,与这些函数发生联系。一旦一个代理包装了一个或多个函数,你就能通过代理来调用这些函数,而无须事先了解包装了哪些函数。
请看例1中的代码,在标号1中,定义一个代理类型Del,由于使用了上下文关键字delegate,所以有点像函数的声明,但与函数声明不同的是,此处声明的是一个代理类型Del的实例,其可包装进任意接受一个int类型作为参数并返回一个int值类型的函数(任意有效的参数列表及返回类型组合都是允许的)。一旦定义了某种代理类型,它只能被用于包装具有同样类型的函数;代理类型可被定义在源文件中或命名空间的范围内,也能定义在类中,并可有public或private访问控制属性。
例1:
静态函数A::Square与实例函数B::Cube对Del来说,都具有相同的参数类型及返回类型,因此它们能被包装进同类型的代理中。注意,即使两个函数均为public,当考虑它们与Del的兼容性时,它们的可访问性也是不相关的,这样的函数也能被定义在相同或不同的类中,主要由程序员来选择。
一旦定义了某种代理类型,就可创建此类型实例的句柄,并进行初始化或赋值操作,如标号2中所示的静态函数A::Square,及标号5中所示的实例函数B::Cube。(此处只是出于演示的目的,否则把Cube做成实例函数没有任何好处。)
创建一个代理实例涉及到调用一个构造函数,如果是在包装一个静态函数,只需传递进一个指向成员函数的指针;而对实例函数而言,必须传递两个参数:一个实例的句柄及指向实例成员函数的指针。
在初始化代理实例之后,就能间接地调用它们包装的函数了,用法与直接调用原函数一样,只不过现在用的是代理实例名,如标号3与6,由包装函数返回的值也是像直接调用函数时那样获得。如果一个代理实例的值为nullptr,此时再试图调用被包装的函数,会导致System::NullReferenceException类型异常。
以下是输出:
传递与返回代理
有时,把包装好的函数传递给另一个函数,会非常有用,接受一方的函数并不知道会传递过来哪个函数,并且它也无须关心,只需简单地通过包装好的代理,间接调用此函数就行了。
下面以集合中元素排序来说明,大多数时候,集合中元素排序所依据的规则,只在对某对元素进行比较的方法上存在区别。如果在运行时提供进行比较的函数,一个排序过程就能用相应定义的比较函数排出任意的顺序,请看例2。
例2:
代理类型的合并
一个代理实例实际上能包装多个函数,在这种情况下,被包装的函数集被维护在一个调用列表中,当合并两个代理实例时,它们的调用列表也以指定的顺序连接起来,并产生一个新的列表,而现有的两个列表并没有发生改变。当从调用列表中移除一个或多个函数时,也会产生一个新的列表,且原始列表不会发生变化。请看例4中的代码,每个函数调用后的输出都写在相应函数后。
例4:
代理可通过 + 和 += 操作符来合并,如标号3、4中所示。两个单入口列表会连接成一个新的双入口列表,以先左操作数,后右操作数的顺序。新的列表被cd3引用,而现有的两个列表并未改变。在此要注意的是,不能合并不同类型的代理。
正如在标号4中所见,同一个函数可在一个调用列表中包装多次;而在标号5中,也说明了一个调用列表能同时包含类与实例函数。代理可通过 - 或 -= 操作符移除,如标号6中所示。
当同一个函数在调用列表中出现多次时,一个对它的移除请求会导致最右边的项被移除。在标号6中,这产生了一个新的三入口列表,其被cd3引用,且前一个列表保持不变(因为先前被cd3引用的列表现在引用计数为零,所以会被垃圾回收)。
当一个调用列表中的最后一项被移除时,代理将为nullptr值,此处没有空调用列表的概念,因为,根本就没有列表了。
例5中演示了另一个代理合并与移除的例子,正如标号3a与3b中所示,两个多入口调用列表是以先左操作数,后右操作数的顺序连接的。
如果想移除一个多入口列表,只有当此列表为整个列表中严格连续的子集时,操作才会成功。例如,在标号4b中,你可以移除F1和F2,因为它们是相邻的,对标号5b中的两个F2及标号6b中的F1、F2来说,道理也是一样的。但是,在标号7b中,列表中有两个连续的F1,所以操作失败,而结果列表则是最开始的列表,它包含有4个入口。
例5:
在前几篇文章中,已经多次演示了如果让一个句柄在不同的时间,被引用至不同的对象,从而以更抽象的方法来解决程序中的问题,但是,也能使用代理通过函数来达到同样的效果;代理是包装了函数的一个对象,且对实例函数而言,也能通过特定的实例,与这些函数发生联系。一旦一个代理包装了一个或多个函数,你就能通过代理来调用这些函数,而无须事先了解包装了哪些函数。
请看例1中的代码,在标号1中,定义一个代理类型Del,由于使用了上下文关键字delegate,所以有点像函数的声明,但与函数声明不同的是,此处声明的是一个代理类型Del的实例,其可包装进任意接受一个int类型作为参数并返回一个int值类型的函数(任意有效的参数列表及返回类型组合都是允许的)。一旦定义了某种代理类型,它只能被用于包装具有同样类型的函数;代理类型可被定义在源文件中或命名空间的范围内,也能定义在类中,并可有public或private访问控制属性。
例1:
| 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; } }; /*1*/ delegate int Del(int value); int main() { /*2*/ Del^ d = gcnew Del(&A::Square); /*3*/ Console::WriteLine("d(10) result = {0}", d(10)); /*4*/ B^ b = gcnew B; /*5*/ d = gcnew Del(b, &B::Cube); /*6*/ Console::WriteLine("d(10) result = {0}", d(10)); } |
静态函数A::Square与实例函数B::Cube对Del来说,都具有相同的参数类型及返回类型,因此它们能被包装进同类型的代理中。注意,即使两个函数均为public,当考虑它们与Del的兼容性时,它们的可访问性也是不相关的,这样的函数也能被定义在相同或不同的类中,主要由程序员来选择。
一旦定义了某种代理类型,就可创建此类型实例的句柄,并进行初始化或赋值操作,如标号2中所示的静态函数A::Square,及标号5中所示的实例函数B::Cube。(此处只是出于演示的目的,否则把Cube做成实例函数没有任何好处。)
创建一个代理实例涉及到调用一个构造函数,如果是在包装一个静态函数,只需传递进一个指向成员函数的指针;而对实例函数而言,必须传递两个参数:一个实例的句柄及指向实例成员函数的指针。
在初始化代理实例之后,就能间接地调用它们包装的函数了,用法与直接调用原函数一样,只不过现在用的是代理实例名,如标号3与6,由包装函数返回的值也是像直接调用函数时那样获得。如果一个代理实例的值为nullptr,此时再试图调用被包装的函数,会导致System::NullReferenceException类型异常。
以下是输出:
| d(10) result = 100 d(10) result = 1000 |
传递与返回代理
有时,把包装好的函数传递给另一个函数,会非常有用,接受一方的函数并不知道会传递过来哪个函数,并且它也无须关心,只需简单地通过包装好的代理,间接调用此函数就行了。
下面以集合中元素排序来说明,大多数时候,集合中元素排序所依据的规则,只在对某对元素进行比较的方法上存在区别。如果在运行时提供进行比较的函数,一个排序过程就能用相应定义的比较函数排出任意的顺序,请看例2。
例2:
| using namespace System; ref struct StrCompare { static int CompareExact(String^ s1, String^ s2) { Console::WriteLine("Comparing {0} and {1} " "using CompareExact", s1, s2); // ... return 0; } static int CompareIgnoreCase(String^ s1, String^ s2) { Console::WriteLine("Comparing {0} and {1}" "using CompareIgnoreCase", s1, s2); // ... return 0; } }; delegate int Compare(String^ s1, String^ s2); /*1*/ Compare^ FindComparisonMethod() { // ... } void Sort(Compare^ compare) { int result; /*3*/ result = compare("Hello", "Hello"); /*4*/ result = compare("Hello", "HELLO"); /*5*/ result = compare("Hello", "Hell"); } int main() { /*6*/ Sort(gcnew Compare(&StrCompare::CompareIgnoreCase)); /*7*/ Sort(FindComparisonMethod()); /*8*/ FindComparisonMethod()("Red", "RED"); } |
Compare代理类型可对任意接受两个String^参数并返回一个int结果的函数进行包装,在此,有两个函数为StrCompare::CompareExact和StrCompare::CompareIgnoreCase。
在标号6中,创建了一个Compare代理类型的实例,用它来包装StrCompare::CompareIgnoreCase,并把此代理句柄传递给Sort函数,其将会利用比较函数进一步进行处理。
正如大家所看到的,Sort可接受一个代理类型的参数--而此参数可像其他函数参数一样,可为传值、传址、传引用。
在标号7中,调用了FindComparisonMethod函数,其返回一个Del代理类型,接着在标号7及8中调用了包装过的函数。此处要重点说一下标号8:首先,FindComparisonMethod函数是被调用来获取代理实例--其常用于调用底层函数;其次,这两个函数的调用操作符都有同等的优先级,所以它们从左至右调用。
FindComparisonMethod函数中也用了一些逻辑用于确定到底需要包装哪个函数,此处就未作详细说明了。
代理类型的兼容性
一个代理类型只与它自身相兼容,与其他任何代理类型都不兼容,即使其他类型的包装函数均为同一类型。请看例3,非常明显,代理类型D1与函数A::M1与A::M2兼容,代理类型D2也与这些函数兼容,然而,这两个代理类型在标号5、6、8、9中并不能互换使用。
例3:
| delegate void D1(); delegate void D2(); public struct A { static void M1() { /* ... */ } static void M2() { /* ... */ } }; void X(D1^ m) { /* ... */ } void Y(D2^ n) { /* ... */ } int main() { D1^ d1; /*1*/ d1 = gcnew D1(&A::M1); //兼容 /*2*/ d1 = gcnew D1(&A::M2); //兼容 D2^ d2; /*3*/ d2 = gcnew D2(&A::M1); //兼容 /*4*/ d2 = gcnew D2(&A::M2); //兼容 /*5*/ d1 = d2; //不兼容 /*6*/ d2 = d1; //不兼容 /*7*/ X(d1); //兼容 /*8*/ X(d2); //不兼容 /*9*/ Y(d1); //不兼容 /*10*/ Y(d2); //兼容 } |
代理类型的合并
一个代理实例实际上能包装多个函数,在这种情况下,被包装的函数集被维护在一个调用列表中,当合并两个代理实例时,它们的调用列表也以指定的顺序连接起来,并产生一个新的列表,而现有的两个列表并没有发生改变。当从调用列表中移除一个或多个函数时,也会产生一个新的列表,且原始列表不会发生变化。请看例4中的代码,每个函数调用后的输出都写在相应函数后。
例4:
| using namespace System; delegate void D(int x); ref struct Actions { static void F1(int i) { Console::WriteLine("Actions::F1: {0}", i); } static void F2(int i) { Console::WriteLine("Actions::F2: {0}", i); } void F3(int i) { Console::WriteLine("instance of Actions::F3: {0}", i); } }; int main() { /*1*/ D^ cd1 = gcnew D(&Actions::F1); //包含F1的调用列表 cd1(10); Actions::F1: 10 /*2*/ D^ cd2 = gcnew D(&Actions::F2); //包含F2的调用列表 cd2(15); Actions::F2: 15 /*3*/ D^ cd3 = cd1 + cd2; //包含F1 + F2的调用列表 cd3(20); Actions::F1: 20 Actions::F2: 20 /*4*/ cd3 += cd1; //包含F1 + F2 + F1的调用列表 cd3(25); Actions::F1: 25 Actions::F2: 25 Actions::F1: 25 Actions^ t = gcnew Actions(); D^ cd4 = gcnew D(t, &Actions::F3); /*5*/ cd3 += cd4; //包含F1 + F2 + F1 + t->F3的调用列表 cd3(30); Actions::F1: 30 Actions::F2: 30 Actions::F1: 30 instance of Actions::F3: 30 /*6*/ cd3 -= cd1; //移除最右边的F1 cd3(35); //调用F1、F2,t->F3 Actions::F1: 35 Actions::F2: 35 instance of Actions::F3: 35 /*7*/ cd3 -= cd4; //移除t->F3 cd3(40); //调用F1、F2 /*8*/ cd3 -= cd1; //移除F1 cd3(45); //调用F2 /*9*/ cd3 -= cd2; //移除F2,调用列表现在为空 /*10*/Console::WriteLine("cd3 = {0}", (cd3 == nullptr ? "null" : "not null")); } Actions::F1: 40 Actions::F2: 40 Actions::F2: 45 cd3 = null |
代理可通过 + 和 += 操作符来合并,如标号3、4中所示。两个单入口列表会连接成一个新的双入口列表,以先左操作数,后右操作数的顺序。新的列表被cd3引用,而现有的两个列表并未改变。在此要注意的是,不能合并不同类型的代理。
正如在标号4中所见,同一个函数可在一个调用列表中包装多次;而在标号5中,也说明了一个调用列表能同时包含类与实例函数。代理可通过 - 或 -= 操作符移除,如标号6中所示。
当同一个函数在调用列表中出现多次时,一个对它的移除请求会导致最右边的项被移除。在标号6中,这产生了一个新的三入口列表,其被cd3引用,且前一个列表保持不变(因为先前被cd3引用的列表现在引用计数为零,所以会被垃圾回收)。
当一个调用列表中的最后一项被移除时,代理将为nullptr值,此处没有空调用列表的概念,因为,根本就没有列表了。
例5中演示了另一个代理合并与移除的例子,正如标号3a与3b中所示,两个多入口调用列表是以先左操作数,后右操作数的顺序连接的。
如果想移除一个多入口列表,只有当此列表为整个列表中严格连续的子集时,操作才会成功。例如,在标号4b中,你可以移除F1和F2,因为它们是相邻的,对标号5b中的两个F2及标号6b中的F1、F2来说,道理也是一样的。但是,在标号7b中,列表中有两个连续的F1,所以操作失败,而结果列表则是最开始的列表,它包含有4个入口。
例5:
| using namespace System; delegate void D(int x); void F1(int i) { Console::WriteLine("F1: {0}", i); } void F2(int i) { Console::WriteLine("F2: {0}", i); } int main() { D^ cd1 = gcnew D(&F1); D^ cd2 = gcnew D(&F2); /*1*/ D^ list1 = cd1 + cd2; // F1 + F2 /*2*/ D^ list2 = cd2 + cd1; // F2 + F1 D^ cd3 = nullptr; /*3a*/ cd3 = list2 + list1; // F2 + F1 + F1 + F2 cd3(10); /*3b*/ cd3 = list1 + list2; // F1 + F2 + F2 + F1 cd3(20); /*4a*/ cd3 = list1 + list2; // F1 + F2 + F2 + F1 /*4b*/ cd3 -= cd1 + cd2; // F2 + F1 cd3(30); /*5a*/ cd3 = list1 + list2; // F1 + F2 + F2 + F1 /*5b*/ cd3 -= cd2 + cd2; // F1 + F1 cd3(40); /*6a*/ cd3 = list1 + list2; // F1 + F2 + F2 + F1 /*6b*/ cd3 -= cd2 + cd1; // F1 + F2 cd3(50); /*7a*/ cd3 = list1 + list2; // F1 + F2 + F2 + F1 /*7b*/ cd3 -= cd1 + cd1; // F1 + F2 + F2 + F1 cd3(60); } |
导入论坛 引用链接 收藏 分享给好友 推荐到圈子 管理 举报
标题搜索
日历
|
|||||||||
| 日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
| 1 | 2 | ||||||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 | |||
| 10 | 11 | 12 | 13 | 14 | 15 | 16 | |||
| 17 | 18 | 19 | 20 | 21 | 22 | 23 | |||
| 24 | 25 | 26 | 27 | 28 | 29 | 30 | |||
| 31 | |||||||||
我的存档
数据统计
- 访问量: 4035
- 日志数: 170
- 建立时间: 2006-12-14
- 更新时间: 2008-04-07

