国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

實例解析C++/CLI之代理與事件

2019-11-17 05:48:04
字體:
來源:轉載
供稿:網友

  在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。

  例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);
}  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) { /* ... */ }
 }
 // ...
};

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 张家川| 新竹市| 湖北省| 旺苍县| 宜州市| 墨脱县| 伊金霍洛旗| 贞丰县| 蓝田县| 玛多县| 桐柏县| 舞钢市| 孝义市| 平邑县| 宾川县| 昭通市| 阿坝县| 朝阳县| 濮阳市| 微山县| 务川| 岢岚县| 荔波县| 景谷| 井研县| 咸阳市| 拜城县| 始兴县| 武乡县| 恭城| 巴马| 揭西县| 陆良县| 鹤峰县| 吐鲁番市| 疏勒县| 泉州市| 玛沁县| 丹巴县| 巴彦县| 松原市|