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

C# 事件封裝本質:為何無法在子類別或外部直接引發事件

     編輯:關於C#

現象觀察

定義基底類別包含兩種事件寫法:


class Base
{
    // 自動實作事件 (編譯器產生私有委派欄位 + add/remove)
    public event Action TestEventA;

    // 手動實作事件 (自行管理委派欄位)
    private Action _testEventB;
    public event Action TestEventB
    {
        add { _testEventB += value; }
        remove { _testEventB -= value; }
    }

    protected void OnTestEventA() { TestEventA?.Invoke(); }
    protected void OnTestEventB() { _testEventB?.Invoke(); }
}

class Child : Base
{
    public void Do()
    {
        // TestEventA(); // 編譯錯誤:事件只能在宣告類別內部引發
    }
}

IL 層面分析

IL 代碼截圖
圖 1:事件編譯後的 IL 結構

兩種事件編譯後結構幾乎相同:

  1. 產生 私有委派欄位 (自動實作為 TestEventA,手動為 _testEventB)。
  2. 產生 add/remove 存取器,內部以 Delegate.Combine/Remove 操作欄位,並以 lock(this) 確保執行緒安全。
  3. 無公開的引發方法:C# 編譯器刻意不產生 raise 存取器,也未將委派欄位設為 protected。

語言設計哲學:封裝與安全

  1. 封裝性:事件是「發生什麼事」的通知,引發時機應由類別內部控制。若允許外部/子類別隨意引發,破壞類別不變項,導致訂閱者收到虛假通知。
  2. 執行緒安全:編譯器產生的 add/remove 已處理同步,若開放欄位直接操作,開發者易忽略同步導致競態條件。
  3. 慣用模式:標準模式為 protected virtual void OnXxx() 供子類別覆寫「引發前邏輯」,而非直接引發事件。

正確的子類別擴充方式


class Child : Base
{
    public void DoSomething()
    {
        // 業務邏輯...
        OnTestEventA(); // 呼叫基底類別受保護的引發方法
    }

    // 覆寫引發邏輯 (如加入日誌、條件判斷)
    protected override void OnTestEventA()
    {
        Console.WriteLine("Child 準備引發 TestEventA");
        base.OnTestEventA();
    }
}

常見錯誤:引發事件前未複製委派

錯誤寫法:

protected void OnTestEventA() { TestEventA(); } // 多執行緒下,Invoke 期間若有移除訂閱,可能 NullReferenceException

正確寫法 (複製委派快照):

protected void OnTestEventA()
{
    var handler = TestEventA; // 讀取欄位到區域變數 (原子操作)
    handler?.Invoke();        // 對快照呼叫,避免競態
}

輕鬆一刻

一位老先生問我如何複製貼上,我說 Ctrl+C 再 Ctrl+V。隔天他說不行,原來他是在公司 Ctrl+C,回家才 Ctrl+V。
真人版:某開發在基底類別定義 Event,子類別 A 引發,子類別 B 訂閱 —— 卻發現 B 收不到通知,因為是不同實例各自擁有獨立事件欄位。

總結

C# 事件設計強制封裝引發權,透過 protected virtual OnXxx 讓子類別參與擴充,確保封裝性、執行緒安全與正確的發布/訂閱語意。若需跨實例通訊,請使用共用的事件來源物件 (如靜態事件、事件聚合器、MediatR 等)。

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