---
# System prepended metadata

title: Delegate(委派)、Event(事件)、EventHandler(標準委派事件)
tags: [Delegate, Event, 事件, EventHandler, 委派, C#]

---

# Delegate(委派)、Event(事件)、EventHandler(標準委派事件)
在查詢相關資料時，看到很多種解釋和用法，但總感覺有些痛點沒有解釋清楚，故在此整理。

以下範例，都將使用這三個方法`加(Add)`、`減(Sub)`、`乘(Multiply)`來做示範。
```csharp=
private void Add(int num1, int num2) { //加
    Console.WriteLine("Add = " + (num1 + num2).ToString());
}

private void Sub(int num1, int num2) { //減
    Console.WriteLine("Sub = " + (num1 - num2).ToString());
}

private void Multiply(int num1, int num2) { //乘
    Console.WriteLine("Multiply = " + (num1 * num2).ToString());
}
```

## Delegate(委派)
>委派(delegate)就像「方法(method)的執行清單」，可以將想執行的方法全部加入清單中，引動(invoke)後，將++依序執行++清單內的所有方法。

:::success
##### 💡「將方法加入清單」的動作，稱為「訂閱」，反之為「取消訂閱」。
:::

### 使用方式
- 📌 **定義一個 委派** - *==想加入清單的方法，必須長這樣==*
    `[存取修飾詞] delegate 回傳值 委派名稱(傳入值1, 傳入值2, ...);`
  - ***傳入值***、***回傳值*** 的型態，必須跟想要訂閱的方法相同。
  - `Add`、`Sub`、`Multiply`的 ***傳入值*** 是兩個`int`，***回傳值*** 為`void`。

  :::success
  ##### 💡「回傳值+名稱+傳入值」的這個組合，稱為「簽章(Signature)」。
  :::
  
```csharp=
public delegate void CalculateMath(int num1, int num2);
```

- 📌 **宣告一個 委派** - *==建立清單==*
    `[存取修飾詞] 委派名稱 委派變數;`
```csharp=+
CalculateMath myDelegate;
```

- 📌 **訂閱/取消訂閱** - *==加入/移出清單==*
```csharp=+
myDelegate += new CalculateMath(Add); //將方法「累加」到清單尾端
myDelegate -= new CalculateMath(Add); //將方法從清單中「移除」

//將方法「設定」到清單(會覆蓋掉原本的清單)
myDelegate = new CalculateMath(Add);
```
也可以簡化寫法
```csharp
myDelegate += Add; //將方法「累加」到清單尾端
myDelegate -= Add; //將方法從清單中「移除」

//將方法「設定」到清單(會覆蓋掉原本的清單)
myDelegate = Add;
```

- 📌 **引動(invoke)委派** - *==執行清單內的方法==*
```csharp=+
myDelegate.Invoke(2, 5);
```
也可以簡化寫法
```csharp
myDelegate(2, 5);
```

### ✔ **完整範例**
```csharp=
/*
建立一個Form，並加入一個Button。
按下Button，會將方法加入Delegate，然後invoke。
*/

public partial class Form1 : Form
{
    public Form1() {
        InitializeComponent();
    }

    public delegate void CalculateMath(int a, int b); //定義Delegate
    CalculateMath myDelegate = null; //宣告Delegate
    
    private void Add(int num1, int num2) { //加
        Console.WriteLine("Add = " + (num1 + num2).ToString());
    }

    private void Sub(int num1, int num2) { //減
        Console.WriteLine("Sub = " + (num1 - num2).ToString());
    }

    private void Multiply(int num1, int num2) { //乘
        Console.WriteLine("Multiply = " + (num1 * num2).ToString());
    }
    
    private void button1_Click(object sender, EventArgs e) {
        myDelegate += new CalculateMath(Add); //清單:{Add}
        myDelegate += new CalculateMath(Sub); //清單:{Add,Sub}
        myDelegate.Invoke(1, 2); //執行清單
        
        myDelegate += new CalculateMath(Multiply); //清單:{Add,Sub,Multiply}
        myDelegate -= new CalculateMath(Sub); //清單:{Add,Multiply}
        myDelegate.Invoke(3, 4); //執行清單
        
        myDelegate = new CalculateMath(Sub); //清單:{Sub}
        myDelegate.Invoke(5, 6); //執行清單
        myDelegate = null; //清空
    }
}
/**********
執行結果:
Add = 3
Sub = -1
Add = 7
Multiply = 12
Sub = -1
**********/
```

- ✅ **範例小細節**
  - `CalculateMath myDelegate = null;` 尚未使用的委派，可先設為`null`。
  - `myDelegate.Invoke(1, 2);` 執行`Invoke`，會**依照加入順序**呼叫方法。
  - `myDelegate = new CalculateMath(Sub);` 用「`=`」會覆蓋整個清單。
  - `myDelegate = null;` 清空委派，直接設為`null`。

## Event(事件)
> Event(事件)就像++加入一些限制的Delegate(委派)++，本質上也是++方法(method)的執行清單++，但宣告時必須指定一個委派，引動(invoke)後會++依序執行++清單內的所有方法。

### 使用方式
- 📌**定義一個 委派** - *==想加入清單的方法，必須長這樣==*
    `[存取修飾詞] delegate 回傳值 委派名稱(傳入值1, 傳入值2, ...);`
```csharp=
public delegate void CalculateMath(int num1, int num2);
```

- 📌**宣告一個 事件** - *==建立清單==*
    `[存取修飾詞] event 委派名稱 事件變數;`
```csharp=+
event CalculateMath myEvent;
```

- 📌 **訂閱/取消訂閱** - *==加入/移出清單==*
```csharp=+
myEvent += new CalculateMath(Add); //將方法「累加」到清單尾端
myEvent -= new CalculateMath(Add); //將方法從清單中「移除」

//將方法「設定」到清單(會覆蓋掉原本的清單)
myEvent = new CalculateMath(Add);
```
也可以簡化寫法
```csharp
myEvent += Add; //將方法「累加」到清單尾端
myEvent -= Add; //將方法從清單中「移除」

//將方法「設定」到清單(會覆蓋掉原本的清單)
myEvent = Add;
```

- 📌 **引動(invoke)事件** - *==執行清單內的方法==*
```csharp=+
myEvent.Invoke(2, 5);
```
也可以簡化寫法
```csharp
myEvent(2, 5);
```

### ✔ **完整範例**
```csharp=
public partial class Form1 : Form
{
    public Form1() {
        InitializeComponent();
    }

    public delegate void CalculateMath(int a, int b); //定義Delegate
    event CalculateMath myEvent = null; //宣告Event
    
    private void Add(int num1, int num2) { //加
        Console.WriteLine("Add = " + (num1 + num2).ToString());
    }

    private void Sub(int num1, int num2) { //減
        Console.WriteLine("Sub = " + (num1 - num2).ToString());
    }

    private void Multiply(int num1, int num2) { //乘
        Console.WriteLine("Multiply = " + (num1 * num2).ToString());
    }
    
    private void button1_Click(object sender, EventArgs e) {
        myEvent += new CalculateMath(Add); //清單:{Add}
        myEvent += new CalculateMath(Sub); //清單:{Add,Sub}
        myEvent.Invoke(1, 2); //執行清單
        
        myEvent += new CalculateMath(Multiply); //清單:{Add,Sub,Multiply}
        myEvent -= new CalculateMath(Sub); //清單:{Add,Multiply}
        myEvent.Invoke(3, 4); //執行清單
        
        myEvent = new CalculateMath(Sub); //清單:{Sub}
        myEvent.Invoke(5, 6); //執行清單
        myEvent = null; //清空
    }
}
/**********
執行結果:
Add = 3
Sub = -1
Add = 7
Multiply = 12
Sub = -1
**********/
```

## Delegate(委派) 和 Event(事件) 的差異
>使用定義好的委派，宣告一個變數，就會得到一個 ++委派變數++。
>若在宣告前，加一個`event`關鍵字，就會得到一個 ++事件變數++。

:::success
##### 💡 `event`其實就像`delegate`的修飾詞。
:::

```csharp
public delegate void CalculateMath(int num1, int num2); //定義委派
CalculateMath myDelegate;    //宣告委派
event CalculateMath myEvent; //宣告事件
```

乍看之下，**委派(delegate)** 和 **事件(event)** 除了宣告有差別，其餘都一樣。

**⚡沒錯！在同個類別(Class)中，兩者用法是一樣的，差異在++不同類別的使用權限++。⚡**

|     | 差異 |
| --- |:---|
| 委派 | 在 **A類別** 宣告的 `delegate`，**B類別** ++可以++使用 `=` 覆蓋清單，或 `Invoke` 執行清單。 |
| 事件 | 在 **A類別** 宣告的 `event`，**B類別** ++不可以++使用 `=` 覆蓋清單，或 `Invoke` 執行清單。 |

:::success
##### 📍 無論在哪個類別宣告`delegate`或`event`，所有類別都能用 `+=` 或 `-=` 修改清單。
:::

### ✔ **完整範例**
在 **A類別** 定義`delegate`並宣告`event`，然後在 **B類別** 操作`event`。

**A類別:**
```csharp=
public class ClassA
{
    ClassB B = new ClassB(); //建立ClassB物件
    
    public delegate void CalculateMath(int a, int b); //定義Delegate
    public event CalculateMath myEvent = null; //宣告Event
    
    public void Add(int num1, int num2) { //加
        Console.WriteLine("Add = " + (num1 + num2).ToString());
    }
    
    void foo() {
        // 自身類別宣告的event，沒有使用限制
        myEvent += new CalculateMath(Add);
        myEvent += new CalculateMath(B.Sub);
        
        myEvent -= new CalculateMath(Add);
        myEvent -= new CalculateMath(B.Sub);
        
        myEvent = new CalculateMath(Add);
        myEvent = new CalculateMath(B.Sub);
        myEvent = null;
        myEvent.Invoke(1, 2);
    }
}
```

**B類別:**
```csharp=
public class ClassB
{
    ClassA A = new ClassA(); //建立ClassA物件

    public void Sub(int num1, int num2) { //減
        Console.WriteLine("Sub = " + (num1 - num2).ToString());
    }
    
    void foo() {
        // 可以用「+=」或「-=」，操作其它類別宣告的event
        A.myEvent += new CalculateMath(A.Add);
        A.myEvent += new CalculateMath(Sub);
        A.myEvent -= new CalculateMath(A.Add);
        A.myEvent -= new CalculateMath(Sub);
        
        // 不可用「=」或「Invoke」，操作其它類別宣告的event
        A.myEvent = new CalculateMath(A.Add); //error
        A.myEvent = new CalculateMath(Sub);   //error
        A.myEvent = null;                     //error
        A.myEvent.Invoke(1, 2);               //error
    }
}
```

## EventHandler(標準委派事件)

> EventHandler(標準委派事件)，其實是微軟已經事先定義好的委派。

在VS中，對`EventHandler`使用`[移至定義]`，實際到中繼層確認。
如下圖，就能發現`EventHandler`是一個委派定義的名稱。
![image](https://hackmd.io/_uploads/Bk1iLJaR1x.png)

既然`EventHandler`是一個++委派名稱++，那當然就可以直接用這個委派名稱，宣告委派或事件。

```csharp
public EventHandler MyDelegate;    //委派
public event EventHandler MyEvent; //事件
```

:::success
##### 💡 當然也可以不使用預設的`EventHandler`，自己撰寫一模一樣的委派定義，但不必多此一舉。
:::

### EventHandler的定義
```csharp
public delegate void EventHandler(object sender, EventArgs e);
```
`EventHandler`的定義是 ++*沒有回傳值*++、++*兩個傳入值*++ 的委派。

- **傳入值**
  - `sender`，`object`型態用來存放物件，通常是觸發該事件的物件。
  - `e`，`EventArgs`類別用來存放參數，通常是該事件需要使用的參數。
- **EventArgs**
  - `EventArgs`類別，通常會讓其它類別來繼承。
  - 這樣就能定義一個專門用來記錄資料的類別，並將此類別的物件，當成參數傳入事件。如果沒有要使用此欄位，建議傳入`EventArgs.Empty`。

```csharp
public class DATA : EventArgs
{
    int num;
    bool flag;
}
```

### EventHandler的用途

>用逐步回推的方式來理解。

- 如果在VS Form建立一個`Button1`，並且將`Button1`點兩下，就會發現產生的`button1_Click`方法，跟`EventHandler`的定義一模一樣。
![image](https://hackmd.io/_uploads/BJ9NQQRAye.png)
- 接著前往這個Form的`Designer.cs`，會找到
![image](https://hackmd.io/_uploads/ryckO7C0yx.png)
- 再進一步找`Click`的定義，會在`Control.cs`的中繼層看到
![image](https://hackmd.io/_uploads/BkrtdX0Rye.png)

原來`Click`是在`Control`類別中，用`EventHandler`宣告的++事件變數++。
雙擊`Button1`後，會先建立`button1_Click`方法。
然後在`Designer.cs`用`Click`訂閱`button1_Click`方法。

也就是說，從「產生`Button1`到按下執行`button1_Click`」，其實就是一連串完整的事件操作。
:::success
##### 💡 所以通常說「C#是 ++事件驅動的程式設計++(Event-Driven Programming)」。
:::

## 常見用法

### 📌批次處理
>將要執行的方法，加入清單中，一次全部觸發。

例如，想在按下`Button1`時，同時觸發`Button2`和`Button3`，就可以直接用`Button1`的`Click`事件，訂閱`button2_Click`和`button3_Click`。

而不是在`button1_Click`方法中，呼叫`button2_Click`、`button3_Click`。

### ✔ **完整範例**
```csharp=
public partial class Form1 : Form
{
    public Form1() {
        InitializeComponent();

        button1.Click += new EventHandler(button2_Click);
        button1.Click += new EventHandler(button3_Click);
    }
    
    private void button1_Click(object sender, EventArgs e) {
        Console.WriteLine("This is button1_Click.");
    }
    
    private void button2_Click(object sender, EventArgs e) {
        Console.WriteLine("This is button2_Click.");
    }
    
    private void button3_Click(object sender, EventArgs e) {
        Console.WriteLine("This is button3_Click.");
    }
}
/**********
按下Button1的結果:
This is button1_Click.
This is button2_Click.
This is button3_Click.
**********/
```

需注意，被訂閱的方法會依序被呼叫，而且 **==不會等待上一個方法執行完畢==**。

若這些方法有先後順序，需要 **==執行結束後，才呼叫清單中的下一個方法==**，必須自行加入等待機制，或是乾脆不要用委派/事件。

### 📌傳遞方法
>將方法當成參數，傳入其它方法。

有時候一大段程式碼，其中一小區塊需要視情況呼叫不同的方法。

例如，現在有一個`Person`類別，當按下`Button1`會產生`student`物件，而按下`Button2`會產生`teacher`物件，並執行對應動作。

📍可能會這樣寫:
```csharp=
public partial class Form1 : Form
{
    public Form1() {
        InitializeComponent();
    }
    
    private void button1_Click(object sender, EventArgs e) {
        Person student;
        student.StudentSayHello();
    }
    
    private void button2_Click(object sender, EventArgs e) {
        Person teacher;
        teacher.TeacherSayHello();
    }
}

public class Person
{
    public void StudentSayHello() { //主要邏輯
        Console.WriteLine("Hello,");
        Console.WriteLine("I am a student,"); //個別流程1
        Console.WriteLine("Nice to meet you.");
    }

    public void TeacherSayHello() { //主要邏輯
        Console.WriteLine("Hello,");
        Console.WriteLine("I am a teacher."); //個別流程2
        Console.WriteLine("Nice to meet you.");
    }
}
```

`StudentSayHello`和`TeacherSayHello`方法，大部分內容是相同的，只有其中一段不同。
若只為了這一段不同的程式碼，將一整串完整的流程寫兩次，不是一個好做法。
因為類別中，就會有兩個撰寫主要邏輯，而且內容相似的方法，若後續要修改，就要改兩次。
`ex. 將「Nice to meet you.」改成「Glad to see you.」。`

📍針對上述問題，將相似的內容做整合，並改用判斷式決定要執行哪一段流程:
```csharp=
public partial class Form1 : Form
{
    public Form1() {
        InitializeComponent();
    }
    
    private void button1_Click(object sender, EventArgs e) {
        Person student;
        student.SayHello(1);
    }
    
    private void button2_Click(object sender, EventArgs e) {
        Person teacher;
        teacher.SayHello(2);
    }
}

public class Person
{ 
    public void SayHello(int num) { //主要邏輯
        Console.WriteLine("Hello,");
        switch (num) {
        case 1: //個別流程1
            Console.WriteLine("I am a student.");
        break;

        case 2: //個別流程2
            Console.WriteLine("I am a teacher.");
        break;
        }
        Console.WriteLine("Nice to meet you.");
    }
}
```

但這樣有個問題，若不同的區塊內容量很多，那判斷式就會變得很長，而且每增加一種職業，還會再變更長，可讀性極差，顯然也不是好解法。

📍若想讓每個物件都能呼叫同一個方法，又能視情況改變執行流程，就可以用委派。

### ✔ **完整範例**
```csharp=
public partial class Form1 : Form
{
    public Form1() {
        InitializeComponent();
    }
    
    private void button1_Click(object sender, EventArgs e) {
        Person student;
        student.SayHello(Person.StudentSayHello);
    }
    
    private void button2_Click(object sender, EventArgs e) {
        Person teacher;
        teacher.SayHello(Person.TeacherSayHello);
    }
}

public class Person
{
    public delegate void myDelegate();
    
    public void SayHello(myDelegate myDel) { //主要邏輯
        Console.WriteLine("Hello,");
        myDel.Invoke();
        Console.WriteLine("Nice to meet you.");
    }
    
    public static void StudentSayHello() { //個別流程1
        Console.WriteLine("I am a student.");
    }

    public static void TeacherSayHello() { //個別流程2
        Console.WriteLine("I am a teacher.");
    }
}
```

這種寫法讓類別內，只會有一個撰寫主要邏輯的方法。
不同的區塊，則是用委派的方式，讓方法像參數一樣傳入主要邏輯的方法中。
在維護、擴充程式碼時，就不會動到主要邏輯，而要修改這些視情況執行的區塊時，可讀性更好。

## 參考資料

[[C#]使用委派(Delegate)與事件(Event)](https://hackmd.io/@BKLiang/csharp_delegate_event)
[EventHandler Delegate](https://learn.microsoft.com/en-us/dotnet/api/system.eventhandler?view=net-9.0)
[什麼是EventHandler?](https://ryanchen34057.github.io/2019/10/12/eventHandlerIntro/)
[C# 函数中（object sender, EventArgs e）参数是什么意思](https://blog.csdn.net/qq_41375318/article/details/118325758)
[C# delegate 委派](https://ithelp.ithome.com.tw/articles/10255691)
[[C#][C#幼幼班]簽章Signature](https://jo-jo.medium.com/c-c-%E5%B9%BC%E5%B9%BC%E7%8F%AD-%E7%B0%BD%E7%AB%A0signature-fa9b04e1a3e2)
[委派 (Delegate)](https://vito-note.blogspot.com/2012/03/delegate.html)
[[C#]委派Delegate](https://jo-jo.medium.com/c-%E5%A7%94%E6%B4%BEdelegate-8d71fa299d32)
[.Net委派(delegate)的簡易解說與用法](https://eric0806.blogspot.com/2015/01/dotnet-delegate-usage.html)
