C#基礎之委託,事件

上善若泪發表於2024-12-01

目錄
  • 1 委託
    • 1.1 簡介
    • 1.2 操作使用
      • 1.2.1 宣告委託(Delegate)
      • 1.2.2 例項化委託(Delegate)
      • 1.2.3 直接呼叫和invoke
      • 1.2.4 Invoke 和 BeginInvoke
    • 1.3 委託的多播
    • 1.4 委託的匿名和lambda
      • 1.4.1 匿名方法
      • 1.4.2 lambda 表示式
    • 1.5 內建委託
      • 1.5.1 Action系列
      • 1.5.2 Func 系列
      • 1.5.3 Predicate
    • 1.6 示例
  • 2 事件
    • 2.1 簡介
    • 2.2 原理
      • 2.2.1 講解
      • 2.2.2 add 和 remove 訪問器
    • 2.3 使用原生委託
    • 2.4 自定義委託
      • 2.4.1 宣告
      • 2.4.2 操作
        • 2.4.2.1 示例一
        • 2.4.2.2 示例二

1 委託

1.1 簡介

C# 中的委託(Delegate)類似於 C 或 C++ 中函式的指標。委託(Delegate) 是存有對某個方法的引用的一種引用型別變數。引用可在執行時被改變。
委託(Delegate)特別用於實現事件回撥方法。所有的委託(Delegate)都派生自 System.Delegate 類。

1.2 操作使用

1.2.1 宣告委託(Delegate)

委託宣告決定了可由該委託引用的方法。委託可指向一個與其具有相同標籤的方法。
例如,假設有一個委託:public delegate int MyDelegate (string s);
上面的委託可被用於引用任何一個帶有一個單一的 string 引數的方法,並返回一個 int 型別變數

宣告委託的語法如下:

delegate <return type> <delegate-name> <parameter list>

1.2.2 例項化委託(Delegate)

一旦宣告瞭委託型別,委託物件必須使用 new 關鍵字來建立,且與一個特定的方法有關。當建立委託時,傳遞到 new 語句的引數就像方法呼叫一樣書寫,但是不帶有引數。例如:

public delegate void printString(string s);
...
printString ps1 = new printString(WriteToScreen);
printString ps2 = new printString(WriteToFile);

下面的例項演示了委託的宣告、例項化和使用,該委託可用於引用帶有一個整型引數的方法,並返回一個整型值。

using System;

delegate int NumberChanger(int n);
namespace DelegateAppl
{
   class TestDelegate
   {
      static int num = 10;
      public static int AddNum(int p)
      {
         num += p;
         return num;
      }

      public static int MultNum(int q)
      {
         num *= q;
         return num;
      }
      public static int getNum()
      {
         return num;
      }

      static void Main(string[] args)
      {
         // 建立委託例項
         NumberChanger nc1 = new NumberChanger(AddNum);
         NumberChanger nc2 = new NumberChanger(MultNum);
         // 使用委託物件呼叫方法
         nc1(25);
         Console.WriteLine("Value of Num: {0}", getNum());
         nc2(5);
         Console.WriteLine("Value of Num: {0}", getNum());
         Console.ReadKey();
      }
   }
}

結果:
Value of Num: 35
Value of Num: 175

注意:呼叫委託對應方法一般是透過invoke方法,但是從 C# 2.0 開始,委託的呼叫可以直接使用方法呼叫語法,而不需要顯式呼叫 Invoke 方法。

1.2.3 直接呼叫和invoke

雖然委託呼叫底層實際上是透過 Invoke 方法實現的,但語法上允許直接呼叫委託,就像呼叫普通方法一樣。換句話說,呼叫委託和直接呼叫 Invoke 方法是等效的。

假設我們有一個 Action 型別的委託:

Action action = () => Console.WriteLine("Hello, Delegate!");

直接呼叫委託:
action(); // 輸出: Hello, Delegate!

透過 Invoke 方法呼叫:
action.Invoke(); // 輸出: Hello, Delegate!

兩種方式的結果完全一樣,因為 () 是對委託物件 Invoke 方法的簡化語法糖

為什麼允許直接呼叫?

  • 簡潔性:如果每次呼叫都必須寫 .Invoke,程式碼顯得冗長。因此,C# 提供了直接呼叫語法,增強程式碼可讀性。
  • 語法糖:編譯器在編譯時會自動將直接呼叫委託語法轉換為 Invoke 方法的呼叫。
    即:action(); 實際被編譯為:action.Invoke();
  • 優先推薦直接呼叫
    直接呼叫的方式更加簡潔可讀,因此在大多數情況下,推薦使用 action() 而不是顯式呼叫 action.Invoke()

為什麼保留 Invoke 方法?雖然直接呼叫語法更方便,但在某些特殊場景下,顯式呼叫 Invoke 方法可能更合適:

  • 反射場景:透過反射呼叫委託時,需要使用 Invoke 方法。
  • 動態場景:在動態生成程式碼或動態委託時,Invoke 方法更明確。
using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        Action action = PrintMessage;

        // 使用反射呼叫 Invoke
        MethodInfo invokeMethod = action.GetType().GetMethod("Invoke");
        invokeMethod.Invoke(action, null);
    }

    static void PrintMessage()
    {
        Console.WriteLine("Hello, Reflection!");
    }
}
輸出:
Hello, Reflection!

1.2.4 Invoke 和 BeginInvoke

委託的 InvokeBeginInvoke 方法分別用於同步非同步呼叫委託。它們的主要區別體現在呼叫方式、執行緒管理和返回結果的處理上。

Invoke 和 BeginInvoke 的區別

特性 Invoke BeginInvoke
呼叫型別 同步呼叫 非同步呼叫
執行緒阻塞 當前執行緒會阻塞,直到方法執行完成 當前執行緒不會阻塞
返回結果 直接返回方法的返回值 返回 IAsyncResult 物件,透過 EndInvoke 獲取返回值
異常處理 異常會直接在呼叫執行緒中丟擲 異常在呼叫 EndInvoke 時丟擲
執行緒使用 在呼叫執行緒上執行方法 線上程池中執行方法
使用場景 方法較快且呼叫執行緒不能被中斷時 方法較慢且需要非同步執行時
  • Invoke:同步呼叫
    定義:Invoke 是同步呼叫,當前執行緒會等待方法執行完畢後再繼續執行後續程式碼。
    特點:
    • 阻塞呼叫:呼叫執行緒會被阻塞,直到被呼叫的方法完成。
    • 返回結果:直接返回被呼叫方法的返回值(如果有)。
    • 異常處理:如果被呼叫的方法丟擲異常,異常會在呼叫執行緒中傳播。
// 定義一個委託
delegate int AddDelegate(int x, int y);

AddDelegate add = (x, y) => x + y;

// 同步呼叫
int result = add.Invoke(3, 4);
Console.WriteLine($"Result: {result}"); // 輸出:Result: 7
  • BeginInvoke:非同步呼叫
    定義:BeginInvoke 是非同步呼叫,立即返回一個 IAsyncResult 物件,並不會阻塞呼叫執行緒。
    特點:
    • 非阻塞呼叫:呼叫執行緒可以繼續執行其他程式碼,而被呼叫的方法在後臺執行緒中執行。
    • 回撥機制:可以透過傳遞迴調方法或輪詢 IAsyncResult 物件來獲取結果。
      需要顯式呼叫 EndInvoke 方法以獲取結果或處理異常。
// 定義一個委託
delegate int AddDelegate(int x, int y);
AddDelegate add = (x, y) =>
{
    Console.WriteLine("Adding...");
    System.Threading.Thread.Sleep(2000); // 模擬耗時操作
    return x + y;
};

// 非同步呼叫
IAsyncResult asyncResult = add.BeginInvoke(3, 4, null, null);
// 主執行緒繼續執行其他任務
Console.WriteLine("Doing other work...");
// 獲取非同步呼叫結果
int result = add.EndInvoke(asyncResult);
Console.WriteLine($"Result: {result}"); // 輸出:Result: 7

BeginInvoke 的回撥,可以透過回撥函式在非同步操作完成後處理結果:

void CallbackMethod(IAsyncResult ar)
{
    // 獲取委託例項
    AddDelegate add = (AddDelegate)ar.AsyncState;
    // 獲取結果
    int result = add.EndInvoke(ar);
    Console.WriteLine($"Result in Callback: {result}");
}

AddDelegate add = (x, y) =>
{
    Console.WriteLine("Adding...");
    System.Threading.Thread.Sleep(2000);
    return x + y;
};

// 非同步呼叫並指定回撥函式
add.BeginInvoke(5, 7, CallbackMethod, add);
// 主執行緒繼續工作
Console.WriteLine("Doing other work...");

注意事項:

  • BeginInvoke 使用執行緒池中的執行緒來執行方法,因此需要注意執行緒池的資源消耗。
    必須呼叫 EndInvoke:
  • 呼叫 BeginInvoke 後,無論是否需要結果,都必須呼叫 EndInvoke,否則可能會導致資源洩漏。
  • 推薦使用 Task 和 async/await:
  • 在現代 C# 中,推薦使用 Task 和 async/await 替代 BeginInvokeEndInvoke,因為它們更易讀且不易出錯。

1.3 委託的多播

委託物件可使用 + 運算子進行合併。一個合併委託呼叫它所合併的兩個委託。只有相同型別的委託可被合併。- 運算子可用於從合併的委託中移除元件委託。
使用委託的這個有用的特點,可以建立一個委託被呼叫時要呼叫的方法的呼叫列表。這被稱為委託的 多播(multicasting),也叫組播
+- 運算子確實可以直接用於委託物件的合併和移除,但這和 +=-= 的用法有所不同。它們的區別主要在於運算場景賦值方式。具體來說

  • +- 運算子:用於直接建立新的委託物件,不影響原始委託。它們不會修改原始委託,而是生成一個新的多播委託物件。
    • 使用 + 合併兩個委託物件,生成一個新的多播委託。
    • 使用 - 從多播委託中移除一個委託,生成一個新的委託物件。
  • +=-= 運算子:用於修改已有的委託例項,直接在原始變數上新增或移除委託。
    • += 將一個委託新增到現有委託鏈上,結果賦給原變數。
    • -= 從現有委託鏈中移除一個委託,結果賦給原變數。

下面的程式演示了委託的多播:

using System;

delegate int NumberChanger(int n);
namespace DelegateAppl
{
   class TestDelegate
   {
      static int num = 10;
      public static int AddNum(int p)
      {
         num += p;
         return num;
      }

      public static int MultNum(int q)
      {
         num *= q;
         return num;
      }
      public static int getNum()
      {
         return num;
      }

      static void Main(string[] args)
      {
         // 建立委託例項
         NumberChanger nc;
         NumberChanger nc1 = new NumberChanger(AddNum);
         NumberChanger nc2 = new NumberChanger(MultNum);
         nc = nc1;
         nc += nc2;
         // 呼叫多播
         nc(5);
         Console.WriteLine("Value of Num: {0}", getNum());
         Console.ReadKey();
      }
   }
}

結果:
Value of Num: 75

注意
在C#中,當使用+=運算子向委託新增方法時,有兩種方式是等效的:

  • 顯式地建立一個新的委託例項並將其新增到現有的委託鏈中
    myDelegate += new NumberChanger(AddNum);
  • 省略new 部分,直接新增方法。C#編譯器會自動為您處理委託的例項化(如果必要的話):myDelegate += AddNum
    這兩種方式在功能上是完全相同的。從C# 2.0開始,第二種方式(省略new關鍵字和委託型別)變得更加流行,因為它更簡潔,並且減少了不必要的程式碼。

1.4 委託的匿名和lambda

二者比較:

特性 匿名方法 (delegate) Lambda 表示式
語法簡潔性 較繁瑣,需要顯式寫出 delegate 關鍵字和引數列表 更簡潔,直接用 (引數) => {} 表達邏輯
表示式形式支援 不支援表示式形式,必須用 {} 包裹邏輯塊 支援表示式形式,單行邏輯可以省略 {} 和 return
捕獲外部變數(閉包) 支援 支援
語法風格 更接近傳統 C# 方法宣告 更現代、函數語言程式設計風格
語義清晰性 delegate 明確表明它是匿名方法 使用 => 運算子,強調簡潔和函式式思想

1.4.1 匿名方法

匿名方法是透過使用 delegate 關鍵字建立委託例項來宣告的。
語法

delegate(parameters) { statement; }

例如:

delegate void NumberChanger(int n);
...
NumberChanger nc = delegate(int x)
{
    Console.WriteLine("Anonymous Method: {0}", x);
};

程式碼塊 Console.WriteLine("Anonymous Method: {0}", x); 是匿名方法的主體。

委託可以透過匿名方法呼叫,也可以透過命名方法呼叫,即,透過向委託物件傳遞方法引數。

using System;

delegate void NumberChanger(int n);
namespace DelegateAppl
{
    class TestDelegate
    {
        static int num = 10;
        public static void AddNum(int p)
        {
            num += p;
            Console.WriteLine("Named Method: {0}", num);
        }

        public static void MultNum(int q)
        {
            num *= q;
            Console.WriteLine("Named Method: {0}", num);
        }

        static void Main(string[] args)
        {
            // 使用匿名方法建立委託例項
            NumberChanger nc = delegate(int x)
            {
               Console.WriteLine("Anonymous Method: {0}", x);
            };
            
            // 使用匿名方法呼叫委託
            nc(10);

            // 使用命名方法例項化委託
            nc =  new NumberChanger(AddNum);
            
            // 使用命名方法呼叫委託
            nc(5);

            // 使用另一個命名方法例項化委託
            nc =  new NumberChanger(MultNum);
            
            // 使用命名方法呼叫委託
            nc(2);
            Console.ReadKey();
        }
    }
}

1.4.2 lambda 表示式

在 C# 2.0 及更高版本中,引入了 lambda 表示式,它是一種更簡潔的語法形式,用於編寫匿名方法。並且 從 C# 2.0 開始對委託的例項化做了簡化,委託型別的例項化在某些情況下可以省略顯式使用 new 關鍵字
使用 lambda 表示式:

using System;

delegate void NumberChanger(int n);

namespace DelegateAppl
{
    class TestDelegate
    {
        static int num = 10;
        public static void AddNum(int p)
        {
            num += p;
            Console.WriteLine("Named Method: {0}", num);
        }

        public static void MultNum(int q)
        {
            num *= q;
            Console.WriteLine("Named Method: {0}", num);
        }

        static void Main(string[] args)
        {
            // 使用 lambda 表示式建立委託例項
            NumberChanger nc = x => Console.WriteLine($"Lambda Expression: {x}");

            // 使用 lambda 表示式呼叫委託
            nc(10);

            // 使用命名方法例項化委託
            nc = new NumberChanger(AddNum);

            // 使用命名方法呼叫委託
            nc(5);

            // 使用另一個命名方法例項化委託
            nc = new NumberChanger(MultNum);

            // 使用命名方法呼叫委託
            nc(2);

            Console.ReadKey();
        }
    }
}

1.5 內建委託

C# 提供了一些內建的泛型委託,可以覆蓋大部分常見場景,主要包括以下幾個

1.5.1 Action系列

Action 是一個用於定義沒有返回值的方法的委託。支援最多 16 個引數的過載。

Action action = () => Console.WriteLine("No parameters");
action();

Action<int, string> actionWithParams = (x, y) => Console.WriteLine($"x: {x}, y: {y}");
actionWithParams(10, "hello");

1.5.2 Func 系列

Func 是一個帶有返回值的泛型委託。最多支援 16 個輸入引數,最後一個泛型引數是返回值的型別,前面的泛型參數列示輸入引數

Func<int, int, int> add = (x, y) => x + y;
int result = add(3, 5);
Console.WriteLine(result); // 輸出 8

1.5.3 Predicate

Predicate<T> 是一個返回 bool 的泛型委託,常用於過濾或條件判斷。

Predicate<int> isEven = x => x % 2 == 0;
bool check = isEven(4);
Console.WriteLine(check); // 輸出 True

1.6 示例

下面的例項演示了委託的用法。委託 printString 可用於引用帶有一個字串作為輸入的方法,並不返回任何東西。

我們使用這個委託來呼叫兩個方法,第一個把字串列印到控制檯,第二個把字串列印到檔案:

using System;
using System.IO;

namespace DelegateAppl
{
   class PrintString
   {
      static FileStream fs;
      static StreamWriter sw;
      // 委託宣告
      public delegate void printString(string s);

      // 該方法列印到控制檯
      public static void WriteToScreen(string str)
      {
         Console.WriteLine("The String is: {0}", str);
      }
      // 該方法列印到檔案
      public static void WriteToFile(string s)
      {
         fs = new FileStream("c:\\message.txt", FileMode.Append, FileAccess.Write);
         sw = new StreamWriter(fs);
         sw.WriteLine(s);
         sw.Flush();
         sw.Close();
         fs.Close();
      }
      // 該方法把委託作為引數,並使用它呼叫方法
      public static void sendString(printString ps)
      {
         ps("Hello World");
      }
      static void Main(string[] args)
      {
         printString ps1 = new printString(WriteToScreen);
         printString ps2 = new printString(WriteToFile);
         sendString(ps1);
         sendString(ps2);
         Console.ReadKey();
      }
   }
}

結果:
The String is: Hello World

2 事件

2.1 簡介

C# 事件(Event)是一種成員,用於將特定的事件通知傳送給訂閱者。事件通常用於實現觀察者模式,它允許一個物件將狀態的變化通知其他物件,而不需要知道這些物件的細節。

事件(Event) 基本上說是一個使用者操作,如按鍵、點選、滑鼠移動等等,或者是一些提示資訊,如系統生成的通知。應用程式需要在事件發生時響應事件。例如,中斷。
C# 中使用事件機制實現執行緒間的通訊。

關鍵點:

  • 宣告委託:定義事件將使用的委託型別。委託是一個函式簽名。
  • 宣告事件:使用 event 關鍵字宣告一個事件。
  • 觸發事件:在適當的時候呼叫事件,通知所有訂閱者。
  • 訂閱和取消訂閱事件:其他類可以透過 +=-= 運算子訂閱和取消訂閱事件。

事件模型五個組成部分:

  • 事件的擁有者
  • 事件成員
  • 事件的響應者
  • 事件處理器
  • 事件訂閱--把事件處理器與事件關聯在一起,本質是一種以委託型別為基礎的

2.2 原理

2.2.1 講解

事件在類中宣告且生成,且透過使用同一個類或其他類中的委託事件處理程式關聯。包含事件的類用於釋出事件。這被稱為 釋出器(publisher) 類。其他接受該事件的類被稱為 訂閱器(subscriber) 類。事件使用 釋出-訂閱(publisher-subscriber) 模型。

  • 釋出器(publisher) 是一個包含事件和委託定義的物件。事件和委託之間的聯絡也定義在這個物件中。釋出器類的物件呼叫這個事件,並通知其他的物件。
  • 訂閱器(subscriber) 是一個接受事件並提供事件處理程式的物件。在釋出器類中的委託呼叫訂閱器(subscriber)類中的方法(事件處理程式)
    在C#中,通常使用 += 運算子來訂閱事件,使用 -= 運算子來取消訂閱事件

2.2.2 add 和 remove 訪問器

自己定義事件的 addremove 訪問器,從而控制事件訂閱和取消訂閱的具體行為。
下面的 EventHandler 是系統自帶 事件,不用宣告

public class EventDemo
{
    private EventHandler _myEvent;
    // 自定義事件
    public event EventHandler MyEvent
    {
        add
        {
            Console.WriteLine("Adding a subscriber");
            _myEvent += value;
        }
        remove
        {
            Console.WriteLine("Removing a subscriber");
            _myEvent -= value;
        }
    }

    public void TriggerEvent()
    {
        _myEvent?.Invoke(this, EventArgs.Empty);
    }
}

class Program
{
    static void Main()
    {
        EventDemo demo = new EventDemo();
        EventHandler handler = (sender, e) => Console.WriteLine("Event triggered!");

        // 訂閱事件
        demo.MyEvent += handler;  // 輸出: Adding a subscriber
        // 觸發事件
        demo.TriggerEvent();      // 輸出: Event triggered!
        // 取消訂閱事件
        demo.MyEvent -= handler;  // 輸出: Removing a subscriber
    }
}

自定義 add 和 remove 訪問器通常在以下場景中使用:

  • 自定義訂閱邏輯:需要記錄訂閱者或對訂閱者進行篩選時。
  • 執行緒安全:確保事件的訂閱和取消訂閱在多執行緒環境下安全。
  • 限制訂閱數量:控制最多隻能有特定數量的訂閱者。
  • 日誌記錄或除錯:每次事件訂閱或取消時記錄相關資訊。

注意事項:

  • 事件是委託的包裝:事件是基於委託的,但它對委託的直接訪問進行了限制,提供了一種更安全的機制來管理委託呼叫。
  • 事件預設行為:如果不需要特殊邏輯,直接使用預設的 add 和 remove,即可滿足大部分場景。
  • 不要直接對事件賦值:只能透過 +=-= 訪問事件。直接賦值(如 MyEvent = null)是不允許的,除非是在宣告類內部。

2.3 使用原生委託

namespace EventExample
{
	class Program
	{
		MyForm form = new MyForm();
		// 寫此處原生對應的 事件可以先寫此處名字,讓visualstudio 自動生成對應引數型別的 事件
		form.Click += form.FormClicked;
		form.ShowDialog();
	}
	class MyForm : Form
	{
		internal void FormClicked(object sender,EventArgs e)
		{
			this.Text = DataTime.Now.ToString();
		}
	}
}

2.4 自定義委託

2.4.1 宣告

在類的內部宣告事件,首先必須宣告該事件的委託型別。
例如:

public delegate void BoilerLogHandler(string status);

然後,宣告事件本身,使用 event 關鍵字:

// 基於上面的委託定義事件
public event BoilerLogHandler BoilerEventLog;

上面的程式碼定義了一個名為 BoilerLogHandler 的委託和一個名為 BoilerEventLog 的事件,該事件在生成的時候會呼叫委託。

2.4.2 操作

2.4.2.1 示例一

以下示例展示瞭如何在 C# 中使用事件:

using System;
namespace EventDemo
{
    // 定義一個委託型別,用於事件處理程式
    public delegate void NotifyEventHandler(object sender, EventArgs e);

    // 釋出者類
    public class ProcessBusinessLogic
    {
        // 宣告事件
        public event NotifyEventHandler ProcessCompleted;

        // 觸發事件的方法
        protected virtual void OnProcessCompleted(EventArgs e)
        {
            ProcessCompleted?.Invoke(this, e);
        }

        // 模擬業務邏輯過程並觸發事件
        public void StartProcess()
        {
            Console.WriteLine("Process Started!");
            // 這裡可以加入實際的業務邏輯

            // 業務邏輯完成,觸發事件
            OnProcessCompleted(EventArgs.Empty);
        }
    }

    // 訂閱者類
    public class EventSubscriber
    {
        public void Subscribe(ProcessBusinessLogic process)
        {
            process.ProcessCompleted += Process_ProcessCompleted;
        }
        private void Process_ProcessCompleted(object sender, EventArgs e)
        {
            Console.WriteLine("Process Completed!");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            ProcessBusinessLogic process = new ProcessBusinessLogic();
            EventSubscriber subscriber = new EventSubscriber();
            // 訂閱事件
            subscriber.Subscribe(process);
            // 啟動過程
            process.StartProcess();
            Console.ReadLine();
        }
    }
}

說明

  • 定義委託型別:
    public delegate void NotifyEventHandler(object sender, EventArgs e);
    這是一個委託型別,它定義了事件處理程式的簽名。通常使用 EventHandlerEventHandler<TEventArgs> 來替代自定義的委託。
  • 宣告事件:
    public event NotifyEventHandler ProcessCompleted;
    這是一個使用 NotifyEventHandler 委託型別的事件。
  • 觸發事件:
protected virtual void OnProcessCompleted(EventArgs e)
{
    ProcessCompleted?.Invoke(this, e);
}

這是一個受保護的方法,用於觸發事件。使用 ?.Invoke 語法來確保只有在有訂閱者時才呼叫事件。

  • 訂閱和取消訂閱事件:
    process.ProcessCompleted += Process_ProcessCompleted;
    訂閱者使用 += 運算子訂閱事件,並定義事件處理程式 Process_ProcessCompleted。

2.4.2.2 示例二

using System;
namespace SimpleEvent
{
  using System;
  /***********釋出器類***********/
  public class EventTest
  {
    private int value;

    public delegate void NumManipulationHandler();

    public event NumManipulationHandler ChangeNum;
    protected virtual void OnNumChanged()
    {
      if ( ChangeNum != null )
      {
        ChangeNum(); /* 事件被觸發 */
      }else {
        Console.WriteLine( "event not fire" );
        Console.ReadKey(); /* 回車繼續 */
      }
    }
    public EventTest()
    {
      int n = 5;
      SetValue( n );
    }
    public void SetValue( int n )
    {
      if ( value != n )
      {
        value = n;
        OnNumChanged();
      }
    }
  }

  /***********訂閱器類***********/
  public class subscribEvent
  {
    public void printf()
    {
      Console.WriteLine( "event fire" );
      Console.ReadKey(); /* 回車繼續 */
    }
  }

  /***********觸發***********/
  public class MainClass
  {
    public static void Main()
    {
      EventTest e = new EventTest(); /* 例項化物件,第一次沒有觸發事件 */
      subscribEvent v = new subscribEvent(); /* 例項化物件 */
      e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); /* 註冊 */
      e.SetValue( 7 );
      e.SetValue( 11 );
    }
  }
}

結果:
event not fire
event fire
event fire

相關文章