首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

深入解析 C++/CLI 委派與事件機制

     編輯:關於C++

前言

在 C++/CLI 中,委派 是一種封裝方法的物件,而 事件 則是一種讓用戶端程式接收通知的機制。這兩項功能構成了 .NET 型別系統中回呼與通知模式的核心基礎。本文將透過實例代碼,詳細解析委派的宣告、實例化、多播合併、相容性規則,以及事件的發布/訂閱模式。

委派基礎:宣告與實例化

委派型別的宣告使用 delegate 關鍵字,語法類似方法簽章,實際上定義的是一個可封裝特定簽章方法的型別。一旦定義,該委派型別只能封裝參數與回傳型別完全相符的方法。

範例 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; }
};

// 宣告委派型別 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::Square
  • 實例方法需傳遞實例控制代碼與方法指標 gcnew Del(b, &B::Cube)
  • 委派實例化後,透過委派名稱呼叫即可間接調用封裝的方法
  • 若委派為 nullptr 再呼叫,會拋出 NullReferenceException

委派作為參數傳遞與回傳

委派可作為方法參數傳遞,實現「策略模式」或回呼機制。經典應用是集合排序時傳入比較函數。

範例 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);

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++ 函數指標行為不同。

範例 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;  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);    // 錯誤
}

委派多播:合併與移除

委派實例可封裝多個方法,形成調用清單。使用 +、+= 合併,-、-= 移除。合併會產生新清單,原清單不變(不可變模式)。

範例 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() {
    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,不存在「空清單」概念
  • 移除多播委派需為連續子序列才會成功

範例 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);
    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 基底類別功能

所有委派型別隱式繼承自 System::Delegate,提供以下實用方法:

  • GetInvocationList():取得調用清單陣列(Delegate^[])
  • Clone():建立委派淺層複製
  • Target 屬性:取得實例方法綁定的目標物件

範例 6:動態遍歷與過濾調用清單


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),外部只能訂閱(+=)或取消訂閱(-=)。

範例 7:伺服器定義事件


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);  // 引發事件
        }
    }
};

範例 8:用戶端訂閱事件


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);
    }
};

範例 9:事件運作示範


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
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved