首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> .NET 實例教學

C# 委派與事件深度解析:從基礎到觀察者模式與框架規範

     編輯:.NET 實例教學

引言:為什麼需要委派?

早期 C# 開發者常面臨「將方法作為參數傳遞」的需求。例如問候功能需支援多國語言,若用 if/else 或 switch 判斷語言再呼叫對應方法,每新增語言都得修改核心邏輯,違反開放封閉原則。委派解決了這個問題:它是一個型別安全的方法參考,讓方法可像變數一樣傳遞、組合、呼叫。

委派基礎:型別安全的方法簽章

// 定義委派型別:無回傳值、接受一個 string 參數
public delegate void GreetingDelegate(string name);

// 具體方法
public static void EnglishGreeting(string name) => Console.WriteLine($"Morning, {name}");
public static void ChineseGreeting(string name) => Console.WriteLine($"早上好, {name}");

// 使用委派作為參數
public static void GreetPeople(string name, GreetingDelegate makeGreeting)
{
    makeGreeting(name); // 呼叫傳入的方法
}

// 呼叫端
GreetPeople("Jimmy", EnglishGreeting);  // Morning, Jimmy
GreetPeople("張三", ChineseGreeting);   // 早上好, 張三

關鍵點:委派宣告語法與方法簽章幾乎相同,只多一個 delegate 關鍵字。編譯器會將其轉換為繼承自 System.MulticastDelegate 的密封類別。

多點廣播:委派鏈結

委派可串連多個方法,呼叫時依序執行:

GreetingDelegate d = EnglishGreeting;
d += ChineseGreeting;  // 串聯
d("Alice"); 
// 輸出:
// Morning, Alice
// 早上好, Alice

d -= EnglishGreeting;  // 移除
d("Bob");              // 僅輸出 早上好, Bob
  • 首次指派用 =,後續串聯用 +=,移除用 -=。
  • 委派為不可變:+= 實際產生新委派實例 (Delegate.Combine),原變數指向新實例。
  • 呼叫前務必檢查 null (C# 6+ 可用 d?.Invoke(args))。

事件:封裝的委派

直接暴露 public 委派欄位會破壞封裝:外部可任意 = 覆寫、清空訂閱者。event 關鍵字限制外部僅能 +=/-=,內部仍可自由呼叫:

public class GreetingManager
{
    // 事件宣告 = 私有委派欄位 + 公開 add/remove 存取器
    public event GreetingDelegate MakeGreet;

    public void GreetPeople(string name)
    {
        MakeGreet?.Invoke(name); // 安全呼叫
    }
}

// 使用端
var gm = new GreetingManager();
gm.MakeGreet += EnglishGreeting;  // OK
gm.MakeGreet += ChineseGreeting;  // OK
// gm.MakeGreet = EnglishGreeting; // 編譯錯誤:事件只能出現在 +=/-= 左側
gm.GreetPeople("Test");

編譯器生成的事件實作

public event GreetingDelegate MakeGreet; 實際編譯為:

// 私有委派欄位
private GreetingDelegate MakeGreet;

// 同步化的 add/remove 存取器
[MethodImpl(MethodImplOptions.Synchronized)]
public void add_MakeGreet(GreetingDelegate value) 
    => MakeGreet = (GreetingDelegate)Delegate.Combine(MakeGreet, value);

[MethodImpl(MethodImplOptions.Synchronized)]
public void remove_MakeGreet(GreetingDelegate value) 
    => MakeGreet = (GreetingDelegate)Delegate.Remove(MakeGreet, value);

因此事件本質是 一對同步化的 add/remove 方法包裝的私有委派。

委派的中間語言 (IL) 面貌

public delegate void GreetingDelegate(string name); 編譯為:

.class public auto ansi sealed GreetingDelegate
       extends [mscorlib]System.MulticastDelegate
{
  .method public hidebysig specialname rtspecialname 
          instance void .ctor(object 'object', native int 'method') runtime managed
  { ... }
  .method public hidebysig newslot virtual 
          instance class [mscorlib]System.IAsyncResult 
          BeginInvoke(string name, class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed
  { ... }
  .method public hidebysig newslot virtual 
          instance void EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
  { ... }
  .method public hidebysig newslot virtual 
          instance void Invoke(string name) runtime managed
  { ... }
}

繼承 MulticastDelegate 意味著天生支援呼叫清單 (invocation list)。

觀察者模式實戰:熱水器、警報器、顯示器

場景:熱水器燒水,溫度超過 95°C 通知所有訂閱者 (警報器響、顯示器閃)。

步驟 1:定義事件參數 (符合 .NET 規範)

public class BoiledEventArgs : EventArgs
{
    public int Temperature { get; }
    public BoiledEventArgs(int temp) => Temperature = temp;
}

步驟 2:主題 定義委派與事件

public class Heater
{
    // .NET 規範:委派名以 EventHandler 結尾,參數 (sender, EventArgs)
    public delegate void BoiledEventHandler(object sender, BoiledEventArgs e);
    public event BoiledEventHandler Boiled;

    protected virtual void OnBoiled(BoiledEventArgs e)
    {
        Boiled?.Invoke(this, e); // 傳送自己作為 sender
    }

    public void BoilWater()
    {
        for (int i = 0; i <= 100; i++)
        {
            if (i > 95) OnBoiled(new BoiledEventArgs(i));
        }
    }
}

步驟 3:觀察者 實作處理方法

public class Alarm
{
    public void MakeAlert(object sender, BoiledEventArgs e)
    {
        var heater = (Heater)sender;
        Console.WriteLine($"Alarm: {heater.Area} - {heater.Model}: 嘀嘀嘀,水已經 {e.Temperature} 度了!");
    }
}

public class Display
{
    public static void ShowMsg(object sender, BoiledEventArgs e)
    {
        var heater = (Heater)sender;
        Console.WriteLine($"Display: {heater.Area} - {heater.Model}: 水快燒開了,當前 {e.Temperature}°C");
    }
}

步驟 4:組裝與執行

var heater = new Heater { Model = "RealFire 001", Area = "China Xian" };
var alarm = new Alarm();

heater.Boiled += alarm.MakeAlert;
heater.Boiled += (new Alarm()).MakeAlert; // 匿名實例
heater.Boiled += Display.ShowMsg;         // 靜態方法

heater.BoilWater();

輸出:每溫度觸發一次,所有訂閱者依序收到通知。

.NET Framework 事件設計規範總結

總結

  1. 委派:型別安全的方法參考,支援多點廣播,是事件的基礎。
  2. 事件:封裝委派的 add/remove,實現發布/訂閱,保護封裝性。
  3. 觀察者模式:事件是語言級的觀察者模式實作,sender + EventArgs 提供豐富上下文。
  4. 規範:遵循 .NET 命名與簽章慣例,可與框架 (WinForms, ASP.NET, WPF) 無縫整合。

掌握委派與事件,不僅能寫出鬆耦合的元件,更能深入理解 .NET 框架內部運作機制。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
規範項目要求範例
委派名稱以 EventHandler 結尾BoiledEventHandler
委派簽章void (object sender, TEventArgs e)TEventArgs : EventArgs
事件名稱委派名移除 EventHandlerBoiled
事件參數類別以 EventArgs 結尾BoiledEventArgs
觸發方法protected virtual void OnXxx(EventArgs e)允許衍生類覆寫/抑制事件