委託
C#的委託,事實上在DI裡面就已經在用了。假設我定義了一個func型別,叫A,然後在下面A(),這裡雖然我沒顯示寫delegete關鍵字,這就已經是委託了。因為C#官方定義了三個封裝好的委託型別:Func<T>、Action<T>、Predicate<T>,這三分別是有輸出結果和沒輸出結果以及輸出bool結果,最後一個不是很常用。
delegate關鍵字最常用的方法是下面這種場景:假設我有一個很複雜的方法,比如要接受五個值傳入,那寫func裡會很麻煩,那不如直接delegete定義好,比如叫A,然後我就可以直接A a,而不用func 寫的老長。
// 不自定义的话
Func<string, int, DateTime, bool, MyConfig, MyResult> handler = ...;
// 自定义 delegate 后
delegate MyResult DataHandler(string name, int count, DateTime time, bool flag, MyConfig config);
DataHandler handler = ...; // 干净多了
另一個好處則是語義化,也就是字面意思上光看這個型別就能讓人知道這是幹嘛的,否則Func後面寫一長串,就得加註釋才能看得明白了。而另外則是 event 功能,需要藉助delegate型別才能將其重新宣告為事件。
/// 先声明一个dalegate
delegate void OnPlayerDead(Player player, string reason);
/// 然后再声明这是一个事件
event OnPlayerDead PlayerDied;
事件的場景是這樣的:可以在需要的時候,一次性、動態的執行多個方法。比如一個佇列,裡面有十個方法呼叫,但某事件發生後,+=或-=,然後最終呼叫這個委託變數,哪怕我一開始寫的是10次,它也許最終只會執行是5次或15次。這在需要靈活執行的地方非好用,也是經典的釋出-訂閱機制。釋出方不關心訂閱者如何操作,而只關注控制釋出結果在何時執行。
事件往往是多播委託的替代。多輪委託指的就是一個delegate很容易在任何地方被重新用=過載,比如直接=null,那這個從字面上看就非常有風險了。就跟繼承一樣,難以預料核實會被過載,一旦出現一次,下游依賴全完。所以才會有事件,用來控制委託的操作範圍:事件只能被增加或減少,而無法過載。也就是受控的多播委託。
// 普通多播委托 —— 外部可以为所欲为
// 大部分情况下直接声明为官方定义好的delegate类型就行了
public Action OnDead;
player.OnDead = null; // ✅ 直接清空,其他模块挂的全没了
player.OnDead(); // ✅ 外部直接触发,不该这样
player.OnDead += MyMethod; // ✅
// event —— 外部只能 +/-
public event Action OnDead;
player.OnDead = null; // ❌ 报错
player.OnDead(); // ❌ 报错
player.OnDead += MyMethod; // ✅
簡單的說,委託就是一種方法的委派,我用一個變數代替了原先的方法,將其作為原先方法的代表來使用。從原方法的角度來看,就是我把自己委託給了被委派的物件。作為元素的時候尤其明顯正因為這種情況下我必須得找一個變數代表原方法,否則我就無法操作。除此之外則是需要動態和靈活操作方法執行的時候,希望動態的執行不定數個方法時尤其明顯。我能想到的一個場景就是用一個run方法,然後接受一個外部傳入的列舉來判斷什麼情況下執行哪個——這種場合我就可以直接用委託,讓事件發生時+=,不需要時-=,然後在最終執行這個被委派的變數即可——只需要一行,什麼方法都不需要了。
我設想的場景不是寫在一起,而是散在各處——比如我專門暴露了一個方法就是給外部使用的被委託變數,定義好(委託是不是+=的時候型別必須是和我定義好的一致)然後集中執行。我作為上級根本不關心你給了幾個,具體幾個你自己看情況加(動態的事件)。這種形式下似乎有點IoC的味道了,具體的執行反轉出去了,方法內部專注於自身的邏輯,而不關心具體數量。這就是上面所說的釋出-訂閱,本質仍然是一種去耦合。