实例理会C++/CLI之署理与事件
当前位置:以往代写 > C/C++ 教程 >实例理会C++/CLI之署理与事件
2019-06-13

实例理会C++/CLI之署理与事件

实例理会C++/CLI之署理与事件

副标题#e#

在C++/CLI中,署理是对函数举办包装的工具;而事件是一种为客户措施提供通知的类机制。

在前几篇文章中,已经多次演示了假如让一个句柄在差异的时间,被引用至差异的工具,从而以更抽象的要领来办理措施中的问题,可是,也能利用署理通过函数来到达同样的结果;署理是包装了函数的一个工具,且对实例函数而言,也能通过特定的实例,与这些函数产生接洽。一旦一个署理包装了一个或多个函数,你就能通过署理来挪用这些函数,而无须事先相识包装了哪些函数。

请看例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。


#p#副标题#e#

正如各人所看到的,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); //兼容
}

#p#副标题#e#

署理范例的归并

#p#分页标题#e#

一个署理实例实际上能包装多个函数,在这种环境下,被包装的函数集被维护在一个挪用列表中,当归并两个署理实例时,它们的挪用列表也以指定的顺序毗连起来,并发生一个新的列表,而现有的两个列表并没有产生改变。当从挪用列表中移除一个或多个函数时,也会发生一个新的列表,且原始列表不会产生变革。请看例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

#p#副标题#e#

署理可通过 + 和 += 操纵符来归并,如标号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);
}

#p#副标题#e#

#p#分页标题#e#

通过添加或删除一个或多个感乐趣的事件,事件列表可在运行时增长或缩减,请看例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);
   }
};

#p#副标题#e#

要从通知列表中移除一个进口,可利用 -= 操纵符,如标号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兼容,就能利用任意的范例。

#p#分页标题#e#

上述例子中利用的事件只不外是微不敷道的一个示例,别的要说明一点,与以前文章中说过的属性一样,此种范例的事件均以private属性自动备份,且自动生成添加(add)与移除(remove)存取措施,为自界说这些存取措施,就必需提供这些函数的界说,如例10中所示,名称add与remove在此为上下文要害字。

例10:

public ref struct Server
{
  // ...
  static event NewMsgEventHandler^ ProcessNewMsg {
   void add(NewMsgEventHandler^ n) { /* ... */ }
   void remove(NewMsgEventHandler^ n) { /* ... */ }
  }
  // ...
};

    关键字:

在线提交作业