---
# System prepended metadata

title: C# Delgate&Event、Action、Fun解說與元件事件互動簡易解說
tags: [Code Sense, Design-Pattern]

---

###### tags: `Code Sense` `Design-Pattern`

# C# Delgate&Event、Action、Fun解說與元件事件互動簡易解說

近期工作上遇到Desktop客製元件，元件間須自行撰寫事件驅動，在過程中會大量使用委派。委派使用為CSharp較進階的語法，對初學者不太好懂，網路上大致的解說與使用情境較沒這麼容易理解，故整理一篇解釋CSharp中的delgate、event、action與function的設計用意並簡易的描述元件互動情境。

## 我們來訂閱妹子IG
先不談正事，大部分人一定都用過IG對吧? 當我們訂閱某個性感妹子的IG時，一定會希望妹子IG能自動通知傳到你的頁面。那此時我們該如何實作這個功能?答案就是實作觀察者模式! 觀察者模式最重要會有兩個物件，一個是被觀察的observable物件，另一個則是observer物件。如果要以觀察者模式實作，想當然observable被觀察者就是我們的正妹，而觀察者則就是我們的訂閱者。

在我們實作正妹的observer物件之前，先宣告通知的介面方法OnStoryChange，如下

``` csharp=
public interface IFollowersBroadcast
{
    void OnStoryChange(string message);
}
```

接著實作被觀察的正妹，被觀察者通常會需要紀錄有哪些觀察者外，接著就是實作兩個方法，一個是訂閱方法(Add)一個取消訂閱方法(Remove)。

我們開始實作正妹IG，被觀察者(正妹)需要記錄哪些男生訂閱了他，所以會有一個IFollowersBroadcast的List資料去記錄有哪些觀察者，並去實作訂閱與取消訂閱這兩件事情。因為我們使用IG做例子，這裡我故意寫成追蹤(Follower)與取消追蹤(UnFllower)。接著就是最重要的通知實作，但正妹動態改變時，則會通知所有的追中者，這裡很簡單的就用一個For去通知每一個追蹤者。

``` csharp=
public class SexyGirlInstagramWithObservablePattern
{
	/// <summary>
	/// event
	/// </summary>
	private List<IFollowersBroadcast> Broadcasts { get; set; }
	public SexyGirlInstagramWithObservablePattern()
	{
		Broadcasts = new List<IFollowersBroadcast>();
	}
	public void Followers(IFollowersBroadcast broadcast)
	{
		Broadcasts.Add(broadcast);
	}
	public void UnFllowers(IFollowersBroadcast broadcast)
	{
		Broadcasts.Remove(broadcast);
	}
	public void BroadCast(string storMessage)
	{
		foreach (var item in Broadcasts)
		{
			item.OnStoryChange("Observable" + storMessage);
		}
	}
}
```

被觀察者實作完後，接著我們去實作觀察者，這裡我用追隨者為物件名稱，程式碼如下，觀察者最重要唯一一件事情就是要去實作被通知事件 IFollowersBroadcast

``` csharp=
public class Follower : IFollowersBroadcast
{
	public string Name { get; private set;}

	public Follower(string name)
	{
		Name = name;
	}

	public void OnStoryChange(string message)
	{
		Console.WriteLine($"{Name} get message=>{message}");
	}

}
```

接著我們要在Context實作使用者訂閱妹子這件事情，程式碼如下

``` csharp=
static void Main(string[] args)
{
	var followerMario = new Follower("Mario");
	var followerTom = new Follower("Tom");

	var sexyGirlObservable = new SexyGirlInstagramWithObservablePattern();
	// Mario追蹤了妹子
	sexyGirlObservable.Followers(followerMario);
	// Tom追蹤了妹子
	sexyGirlObservable.Followers(followerTom);
	// 妹子發布動態
	sexyGirlObservable.BroadCast("I am so beautiful");
	// Tom取消追蹤
	sexyGirlObservable.UnFllowers(followerTom);
	// 妹子又發了一次動態
	sexyGirlObservable.BroadCast("I am so beautiful!!!!");

	Console.ReadLine();
}
```

結果如下，可看到在Mario與Tom追蹤正妹後，正妹一發布我很正，Mario與Tom就會收到動態消息。而在第二次發布我很正時，因為Tom取消追蹤，所以只有Mario能收到訊息。

![](https://i.imgur.com/yv6zMbH.png)

## 訂閱改成Delgate

除了觀察者模式外，Delgate有可以達到同樣的事情。

Delgate語意上稱呼為委派，有託付任務的涵義。上述IG的例子，我們也可以用委派實作。將"通知"這件任務由委派去處理。在C#中，有delgate關字的委派宣告，宣告委派需要設置相對事件涵式的Format如下，有點像是宣告一個方法記憶體的容器，當使用Invoke時，則就會到註冊的方法記憶體位置去執行。

``` csharp=
public delegate void BroadcastOnStoryChangeHandler(string message);
```
基本上delgate有點像是宣告一個方法記憶體的容器，雖然我們可以單純的使用delgate的Add與Remove，但我們還是做一個簡單的封裝，避免使用者直接操作delgate，他就很像觀察者模式，只是實際註冊的是方法的記憶體位置

``` csharp=
public class SexyGirlInstagramWithDelgate
{
	/// <summary>
	/// 模擬List<IBroadCast>
	/// </summary>
	private List<BroadcastOnStoryChangeHandler> OnBroadcastOnStoryChangeHandler = new List<BroadcastOnStoryChangeHandler>();

	public void Followers(BroadcastOnStoryChangeHandler broadcastOnStoryChangeHandler)
	{
		OnBroadcastOnStoryChangeHandler.Add(broadcastOnStoryChangeHandler);
	}

	public void UnFllowers(BroadcastOnStoryChangeHandler broadcastOnStoryChangeHandler)
	{
		OnBroadcastOnStoryChangeHandler.Remove(broadcastOnStoryChangeHandler);
	}

	public void BroadCast(string storyMessage)
	{
		foreach (var item in OnBroadcastOnStoryChangeHandler)
		{
			item.Invoke("Delgate:" + storyMessage);
		}
	}
}
```

純用delgate大體上會跟觀察者模式很像，委派其實就是起源於觀察者設計模式!但用委派就只需專注在"事件"註冊即可，他相依事件本身，而不是相依整個物件。使用Context如下

``` csharp=
static void Main(string[] args)
{
	var followerMario = new Follower("Mario");
	var followerTom = new Follower("Tom");
    var bigFollower = new BigFollower("Nathan");
	
    var sexyGirlDelgate = new SexyGirlInstagramWithDelgate();
	// Mario追蹤了妹子
	sexyGirlDelgate.Followers(followerMario.OnStoryChange);
	// Tom追蹤了妹子
	sexyGirlDelgate.Followers(followerTom.OnStoryChange);
	// Nathan追蹤了妹子
	sexyGirlDelgate.Followers(bigFollower.OnStoryChange);
	sexyGirlDelgate.BroadCast("I am so beautiful");
	// Mario被老婆發現趕快退訂閱
	sexyGirlDelgate.UnFllowers(followerMario.OnStoryChange);
	sexyGirlDelgate.BroadCast("I am so beautiful!!!!");

	Console.ReadLine();
}
```

結果如下，但如果只有這樣，其實跟觀察者模式也沒有相差太多....所以C#有了event的設計。
![](https://i.imgur.com/TsalCT3.png)

## Delgate與Event

Delgate通常我們會搭配Event使用，Event其實就是一個觀察者模式的封裝

上述例子，我們針對正妹發布動態宣告一個event型態如下(BroadcastOnStoryChangeHandler為delgate)

``` csharp=
// 相當於 List<BroadcastOnStoryChangeHandler> OnBroadcastOnStoryChangeHandler 
// 相當於 Followers(BroadcastOnStoryChangeHandler broadcastOnStoryChangeHandler)
// 相當於  UnFllowers(BroadcastOnStoryChangeHandler broadcastOnStoryChangeHandler)
public event BroadcastOnStoryChangeHandler OnBroadcastOnStoryChangeHandler;
```

使用event時，我們就不需要額外實作Followers與UnFllowers。event本身就是觀察者模式的封裝。我們只需要用+=與-=，就可以去註冊事件。

另外一個很重要的點在於event可以封裝delgate，簡單來說就是避免delgate被直接使用，如果能被直接使用，代表他人就可以輕易取消掉你的註冊!完整code如下

``` csharp=
public class SexyGirlInstagramWithEvent
{
	// 相當於 List<BroadcastOnStoryChangeHandler> OnBroadcastOnStoryChangeHandler 
	// 相當於 Followers(BroadcastOnStoryChangeHandler broadcastOnStoryChangeHandler)
	// 相當於  UnFllowers(BroadcastOnStoryChangeHandler broadcastOnStoryChangeHandler)
	public event BroadcastOnStoryChangeHandler OnBroadcastOnStoryChangeHandler;

	public void BroadCast(string message)
	{
		OnBroadcastOnStoryChangeHandler?.Invoke("Event:"+message);
	}
}
```

竟然沒有實作Followers與UnFllowers，那我們該如何註冊?我們使用+=或者-=當作實際註冊與解註冊，實際Cotext Code如下

``` csharp=
static void Main(string[] args)
{
	var followerMario = new Follower("Mario");
	var followerTom = new Follower("Tom");
	
    +var sexyGirlEvent = new SexyGirlInstagramWithEvent();
	//  Mario追蹤了妹子
	sexyGirlEvent.OnBroadcastOnStoryChangeHandler += followerMario.OnStoryChange;
	// Tom追蹤了妹子
	sexyGirlEvent.OnBroadcastOnStoryChangeHandler += followerTom.OnStoryChange;
	sexyGirlEvent.BroadCast("I am so beautiful");
	// Tom取消追蹤了妹子
	sexyGirlEvent.OnBroadcastOnStoryChangeHandler -= followerTom.OnStoryChange;
	sexyGirlEvent.BroadCast("I am so beautiful");

	Console.ReadLine();
}
```

實際結果如下，基本上會了event，就能做很多物件間通知的彈性設計，但C#提供一個更簡易的方法就是Action與Func，泛型重載委派，整合了event與delgate的用法。

![](https://i.imgur.com/5q9czl8.png)

## Action

Action稱為重載委派，整合event與delgate，差異在使用上程式設計師能快速實作事件通知，不需要則額外宣告delgate跟event。若在正妹IG例子，我們使用Action需宣告一個BroadcastOnStoryChangeHandler事件，且須帶入發布的message string，則設定宣告方法如下

``` csharp=
public Action<string> BroadcastOnStoryChangeHandler;
``` 

! 只有這樣? 沒錯，其實他其實就有點等同於 delegate BroadcastOnStoryChangeHandler(string arg)然後搭event。個人覺得他除了就是讓你少打一些字快速使用外其餘沒太大差別(帶入參數在Action<type1,type2,type3...>設置)。完成Code如下，因為我們需要帶入發布內容，有一個string的帶入參數型態，所以則需宣告Action<string>，相對的如果我們要跟著帶入例如時間之類的就宣告成Action<string, DataTime>。

```csharp=
public class SexyGirlInstagramWithAction
{

	public Action<string> BroadcastOnStoryChangeHandler;
    
	public void BroadCast(string message)
	{
		BroadcastOnStoryChangeHandler?.Invoke("Event:" + message);
	}
}
```

使用Context如下,有沒有感覺跟event整個超像!只是實作上些微不同，Action感覺會用得更直覺。

```csharp=
static void Main(string[] args)
{
	var followerMario = new Follower("Mario");
	var followerTom = new Follower("Tom");

	var sexyGirlAction = new SexyGirlInstagramWithAction();
	//  Mario追蹤了妹子
	sexyGirlAction.BroadcastOnStoryChangeHandler += followerMario.OnStoryChange;
	// Tom追蹤了妹子
	sexyGirlAction.BroadcastOnStoryChangeHandler += followerTom.OnStoryChange;
	sexyGirlAction.BroadCast("I am so beautiful");
	// Tom取消追蹤了妹子
	sexyGirlAction.BroadcastOnStoryChangeHandler -= followerTom.OnStoryChange;
	sexyGirlAction.BroadCast("I am so beautiful");
	Console.ReadLine();
}
```

另外有另一個可回傳值的封裝叫Func，使用上大同小異，差異在設置時，如果有兩個參數以上，最後一個參數則就是回傳型態類別。例如Func<int,string>，代表帶入int參數，回傳型態類別則為string。

## 元件互動

基本上在客製元件時，元件間的互動則就會很常使用到事件(訂閱者模式)，例如下述為3D Switch專案中的MEMS Carrier View，在點選Chip狀態時，GridView的背景色也需要跟著連動。

![](https://i.imgur.com/Gf7FzLI.png)

我們使用Action模擬實作Element互動，首先我們先建置CarrierViewElement物件，並用Action宣告ChangeChipStatus事件，我們需要帶入Chip ID(int)與Status(string)，因此Action Type設置上要有一個int與string(<int,string>)如果用delgate則就是public delgate ChangeChipStatus(int chipID, string status))。

接著宣告SetChipNoStatus方法實作，觸發時使用Invoke通知Actio事件

```csharp=
public class CarrierViewElement
{
	public Action<int, string> ChangeChipStatus;
	public void SetChipNoStatus(int chipNo, string status) => ChangeChipStatus.Invoke(chipNo, status);
}
```
接著建置GridView物件，宣告一個OnChipStatusChange方法(此方法會註冊到CarrierViewElement的ChangeChipStatus事件中)

```csharp=
public class GridViewElement
{
	public void OnChipStatusChange(int chipNo, string status) => Console.WriteLine($"收到{chipNo}Chip改變{status}狀態，做變色處理");
}
```
實作Context如下，我們模擬簡易應用

```csharp=
static void Main(string[] args)
{
	var carrierView = new CarrierViewElement();
	var gridviewElement = new GridViewElement();
    // 註冊事件
	carrierView.ChangeChipStatus += gridviewElement.OnChipStatusChange;
    // 設置ChipNo Status
	carrierView.SetChipNoStatus(1,"Cut Off");
}

```
當carrierView呼叫SetChipNoStatus(WindowsForm則是滑鼠事件去觸發)，當下則就會去通知GridViewElement，此時GridViewElement收到通知後就可以做相對應的動作與處理。

當你學會了事件驅動(委派)後，在需求時做上的設計彈性與完成客製功能的實作能力就能大大增加!