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

.NET 委派演進史:從緊耦合到事件模式的 C# 故事

     編輯:關於C語言

序章:彼得與老闆的糾葛

想像一個場景:勤勞的工人彼得,吝嗇且多疑的老闆。老闆想隨時掌握進度,彼得卻不想被盯著。這個「通知對方」的需求,正是委派模式誕生的緣起。本文以故事形式,層層剖析從硬編碼耦合 → 介面 → 委派 → 事件 → 非同步模式的演進邏輯。

第一幕:緊耦合 —— 硬編碼的依賴

class Worker
{
    private Boss _boss;
    public void Advise(Boss boss) => _boss = boss;

    public void DoWork()
    {
        Console.WriteLine("工作: 開始");
        _boss?.WorkStarted();

        Console.WriteLine("工作: 進行中");
        _boss?.WorkProgressing();

        Console.WriteLine("工作: 完成");
        if (_boss != null)
        {
            int grade = _boss.WorkCompleted();
            Console.WriteLine($"工人得分:{grade}");
        }
    }
}

class Boss
{
    public void WorkStarted() { /* 不關心 */ }
    public void WorkProgressing() { /* 不關心 */ }
    public int WorkCompleted() => 2; // 總分 10 分
}

缺點:Worker 強依賴 Boss 具體類別,無法替換、無法多播、違反開放封閉原則。

第二幕:介面 —— 抽象出契約


interface IWorkerEvents
{
    void WorkStarted();
    void WorkProgressing();
    int WorkCompleted();
}

class Worker
{
    private IWorkerEvents _events;
    public void Advise(IWorkerEvents events) => _events = events;
    // DoWork 內呼叫 _events.xxx()
}

class Boss : IWorkerEvents { /* 實作介面 */ }
class Universe : IWorkerEvents { /* 另一個監聽者 */ }

進步:解耦具體類別,支援多型。
痛點:介面粒度過粗,Boss 被迫實作不關心的方法(介面隔離原則違反);單一 Worker 只能掛一組監聽者(_events 為單一參考)。

第三幕:委派 —— 細粒度的方法參考


delegate void WorkStarted();
delegate void WorkProgressing();
delegate int WorkCompleted();

class Worker
{
    public WorkStarted Started;
    public WorkProgressing Progressing;
    public WorkCompleted Completed;

    public void DoWork()
    {
        Console.WriteLine("工作: 開始");
        Started?.Invoke();

        Console.WriteLine("工作: 進行中");
        Progressing?.Invoke();

        Console.WriteLine("工作: 完成");
        if (Completed != null)
        {
            int grade = Completed.Invoke();
            Console.WriteLine($"工人得分:{grade}");
        }
    }
}

class Boss
{
    public int WorkCompleted() { Console.WriteLine("Better..."); return 4; }
}

class Universe
{
    static void WorkerStartedWork() => Console.WriteLine("宇宙注意到工人開始工作");
    static int WorkerCompletedWork() { Console.WriteLine("宇宙對工人工作感到欣慰"); return 7; }
}

// 使用
var peter = new Worker();
var boss = new Boss();
peter.Completed = new WorkCompleted(boss.WorkCompleted); // 實例方法
peter.Started = new WorkStarted(Universe.WorkerStartedWork); // 靜態方法

關鍵突破:

  • 每個事件獨立掛載,想通知誰就掛誰,符合介面隔離原則。
  • 支援實例方法、靜態方法、Lambda、匿名方法。
  • 委派本質是「型別安全的函式指標」,內部維護調用清單。

第四幕:事件 —— 封裝與存取控制

委派欄位若為 public,外部可任意覆寫 peter.Completed = null 或直接 peter.Completed() 竊取觸發權。event 關鍵字為委派加上「屬性式」存取器,僅允許 += / -=。


class Worker
{
    public event WorkStarted Started;
    public event WorkProgressing Progressing;
    public event WorkCompleted Completed;
    // DoWork 內部仍可直接 Invoke()
}

編譯器自動產生 add_Started/remove_Started 方法,保護調用清單不被外部清空或盜用。

第五幕:收割多播結果

多個監聽者都回傳分數時,預設只取最後一個。需手動遍歷 GetInvocationList():


if (Completed != null)
{
    foreach (WorkCompleted wc in Completed.GetInvocationList())
    {
        int grade = wc.Invoke();
        Console.WriteLine($"收到分數:{grade}");
    }
}

第六幕:非同步模式 —— 不阻塞工人的通知

當監聽者處理耗時(如寫入資料庫、呼叫 Web API),同步 Invoke 會卡住 Worker。三種非同步策略:

1. Fire-and-Forget(發射後不管)


foreach (var wc in Completed.GetInvocationList())
    wc.BeginInvoke(null, null); // 執行緒池執行,結果丟棄

2. 輪詢模式


foreach (var wc in Completed.GetInvocationList())
{
    var ar = wc.BeginInvoke(null, null);
    while (!ar.IsCompleted) Thread.Sleep(1); // 占用 CPU,不推薦
    int grade = wc.EndInvoke(ar);
}

3. 回調模式(推薦)


foreach (var wc in Completed.GetInvocationList())
    wc.BeginInvoke(WorkGraded, wc); // 完成時呼叫 WorkGraded

void WorkGraded(IAsyncResult ar)
{
    var wc = (WorkCompleted)ar.AsyncState;
    int grade = wc.EndInvoke(ar);
    Console.WriteLine($"非同步收到分數:{grade}");
}

注意:回調在執行緒池執行緒執行,若涉及 UI 更新需切回 UI 執行緒(Control.BeginInvoke / Dispatcher.Invoke)。

現代視角:Task 與 async/await

自 .NET 4.5 起,Task 與 async/await 取代了 APM(Begin/End)模式:


// 將委派包裝為 Task
Func> asyncWork = () => Task.Run(() => boss.WorkCompleted());

// 並行等待所有
var tasks = Completed.GetInvocationList()
    .Cast()
    .Select(d => Task.Run(() => d()));
var grades = await Task.WhenAll(tasks);

優點:例外處理統一、可取消、進度報告、避免回調地獄。

總結

從彼得的故事看委派演進:

  1. 緊耦合 → 具體類別依賴
  2. 介面 → 契約抽象,但粒度粗、單播
  3. 委派 → 細粒度、多播、支援靜態/實例方法
  4. 事件 → 封裝存取,防止外部竊取觸發權
  5. 非同步委派 → 不阻塞發送者,APM 模式
  6. Task/async-await → 現代非同步標準,更安全、可組合

理解這條演進脈絡,不僅能寫出更優雅的事件驅動程式碼,更能深刻體會 .NET 設計哲學的核心——「關注點分離」與「控制反轉」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved