在 C++/CLI 中,委派 是一種封裝方法的物件,而 事件 則是一種讓用戶端程式接收通知的機制。這兩項功能構成了 .NET 型別系統中回呼與通知模式的核心基礎。本文將透過實例代碼,詳細解析委派的宣告、實例化、多播合併、相容性規則,以及事件的發布/訂閱模式。
委派型別的宣告使用 delegate 關鍵字,語法類似方法簽章,實際上定義的是一個可封裝特定簽章方法的型別。一旦定義,該委派型別只能封裝參數與回傳型別完全相符的方法。
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; }
};
// 宣告委派型別 Del:接受 int、回傳 int
delegate int Del(int value);
int main() {
// 封裝靜態方法 A::Square
Del^ d = gcnew Del(&A::Square);
Console::WriteLine("d(10) result = {0}", d(10)); // 輸出 100
// 封裝實例方法 B::Cube
B^ b = gcnew B;
d = gcnew Del(b, &B::Cube);
Console::WriteLine("d(10) result = {0}", d(10)); // 輸出 1000
}
關鍵點:
&A::Squaregcnew Del(b, &B::Cube)nullptr 再呼叫,會拋出 NullReferenceException委派可作為方法參數傳遞,實現「策略模式」或回呼機制。經典應用是集合排序時傳入比較函數。
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);
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");
}
Sort 方法不需知道具體比較邏輯,只需透過委派間接呼叫,極大提升彈性。
委派型別具有嚴格的型別安全性:即使兩個委派型別的方法簽章完全相同,它們也不相容。這與 C++ 函數指標行為不同。
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; D2^ d2;
d1 = gcnew D1(&A::M1); // 相容
d1 = gcnew D1(&A::M2); // 相容
d2 = gcnew D2(&A::M1); // 相容
d2 = gcnew D2(&A::M2); // 相容
d1 = d2; // 錯誤:型別不相容
d2 = d1; // 錯誤
X(d2); // 錯誤
Y(d1); // 錯誤
}
委派實例可封裝多個方法,形成調用清單。使用 +、+= 合併,-、-= 移除。合併會產生新清單,原清單不變(不可變模式)。
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() {
D^ cd1 = gcnew D(&Actions::F1); // 清單:F1
D^ cd2 = gcnew D(&Actions::F2); // 清單:F2
D^ cd3 = cd1 + cd2; // 清單:F1, F2
cd3(20); // 依序呼叫 F1(20), F2(20)
cd3 += cd1; // 清單:F1, F2, F1
cd3(25); // F1, F2, F1 各呼叫一次
Actions^ t = gcnew Actions();
D^ cd4 = gcnew D(t, &Actions::F3);
cd3 += cd4; // 清單:F1, F2, F1, t->F3
cd3 -= cd1; // 移除最右側的 F1
cd3 -= cd4; // 移除 t->F3
cd3 -= cd2; // 移除 F2,清單變空,cd3 變 nullptr
}
規則總結:
nullptr,不存在「空清單」概念
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);
D^ list1 = cd1 + cd2; // F1, F2
D^ list2 = cd2 + cd1; // F2, F1
D^ cd3 = list2 + list1; // F2, F1, F1, F2
cd3 -= cd1 + cd2; // 成功移除連續子序列 F1, F2 → 剩 F2, F1
cd3 = list1 + list2; // F1, F2, F2, F1
cd3 -= cd2 + cd2; // 成功移除連續 F2, F2 → 剩 F1, F1
cd3 = list1 + list2; // F1, F2, F2, F1
cd3 -= cd2 + cd1; // 成功移除連續 F2, F1 → 剩 F1, F2
cd3 = list1 + list2; // F1, F2, F2, F1
cd3 -= cd1 + cd1; // 失敗:無連續 F1, F1 → 清單不變
}
所有委派型別隱式繼承自 System::Delegate,提供以下實用方法:
GetInvocationList():取得調用清單陣列(Delegate^[])Clone():建立委派淺層複製Target 屬性:取得實例方法綁定的目標物件
using namespace System;
delegate void D(int x);
ref class Test {
String^ objName;
public:
Test(String^ n) : objName(n) {}
void M(int i) { Console::WriteLine("Object {0}: {1}", objName, i); }
};
void ProcessList(D^ del, int value, Object^ objToExclude) {
if (del == nullptr) return;
if (objToExclude == nullptr) {
del(value); // 直接呼叫所有
} else {
// 逐一檢查並過濾
array^ list = del->GetInvocationList();
for each (Delegate^ d in list) {
if (d->Target != objToExclude) {
((D^)d)(value); // 需轉型回原委派型別
}
}
}
}
int main() {
Test^ t1 = gcnew Test("t1");
Test^ t2 = gcnew Test("t2");
Test^ t3 = gcnew Test("t3");
D^ cd1 = gcnew D(t1, &Test::M);
D^ cd2 = gcnew D(t2, &Test::M);
D^ cd3 = gcnew D(t3, &Test::M);
D^ list = cd1 + cd2 + cd3 + cd2; // t1, t2, t3, t2
ProcessList(list, 100, nullptr); // 全部呼叫
ProcessList(list, 200, t1); // 排除 t1
ProcessList(list, 300, t2); // 排除 t2(兩個入口)
D^ cd4 = cd1 + cd2;
D^ cd5 = (D^)cd4->Clone(); // 複製委派
ProcessList(cd4, 5, nullptr);
ProcessList(cd5, 6, nullptr);
}
事件是基於委派的封裝,提供受控的發布/訂閱模式。事件只能在定義它的類別內部引發(raise),外部只能訂閱(+=)或取消訂閱(-=)。
using namespace System;
public ref struct Server {
// 慣例:事件處理委派命名以 EventHandler 結尾
delegate void NewMsgEventHandler(String^ msg);
// 宣告事件
static event NewMsgEventHandler^ ProcessNewMsg;
static void Broadcast(String^ msg) {
if (ProcessNewMsg != nullptr) {
ProcessNewMsg(msg); // 引發事件
}
}
};
public ref class Client {
String^ clientName;
void ProcessNewMsg(String^ msg) {
Console::WriteLine("Client {0} received message {1}", clientName, msg);
}
public:
Client(String^ name) : clientName(name) {
// 訂閱事件
Server::ProcessNewMsg += gcnew Server::NewMsgEventHandler(this, &Client::ProcessNewMsg);
}
~Client() {
// 解構時取消訂閱
Server::ProcessNewMsg -= gcnew Server::NewMsgEventHandler(this, &Client::ProcessNewMsg);
}
};
int main() {
Server::Broadcast("Message 1"); // 無訂閱者,無輸出
Client^ c1 = gcnew Client("A");
Server::Broadcast("Message 2"); // A 收到
Client^ c2 = gcnew Client("B");
Server::Broadcast("Message 3"); // A, B 收到
Client^ c3 = gcnew Client("C");
Server::Broadcast("Message 4"); // A, B, C 收到
c1->~Client(); // 顯式解構,取消訂閱
Server::Broadcast("Message 5"); // B, C 收到
c2->~Client();
Server::Broadcast("Message 6"); // C 收到
c3->~Client();
Server::Broadcast("Message 7"); // 無訂閱者
}
輸出:
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
事件預設有隱式 add/remove 存取器,可自訂以加入邏輯(如記錄、驗證、執行緒同步):
public ref struct Server {
delegate void NewMsgEventHandler(String^ msg);
static event NewMsgEventHandler^ ProcessNewMsg {
void add(NewMsgEventHandler^ n) {
// 自訂訂閱邏輯
}
void remove(NewMsgEventHandler^ n) {
// 自訂取消訂閱邏輯
}
}
};
System::Delegate 提供清單遍歷、複製等進階功能EventHandler 後綴,支援自訂 add/remove