在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^ compare)
{
int result;
/*
*/ result = compare(
Hello
Hello
);
/*
*/ result = compare(
Hello
HELLO
);
/*
*/ result = compare(
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(); //調用FFt>F
Actions::F:
Actions::F:
instance of Actions::F:
/**/ cd = cd; //移除t>F
cd(); //調用FF
/**/ 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中的FF來說道理也是一樣的但是在標號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^類型當原始及克隆的代理在標號cd中調用時結果輸出如下
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在此為上下文關鍵字
例
public ref struct Server
{
//
static event NewMsgEventHandler^ ProcessNewMsg {
void add(NewMsgEventHandler^ n) { /* */ }
void remove(NewMsgEventHandler^ n) { /* */ }
}
//
};
From:http://tw.wingwit.com/Article/program/net/201311/11477.html