委托是個說爛了的話題,但是依舊有好多人不知道為什麼要在C 中使用委托,最近有朋友也問到我這個問題,所以舉例些場景,以供那些知道怎麼聲明委托、怎麼調用卻不知道為什麼要用的朋友一些參考,當然也是希望驗證下自己的理解是否正確。 如何聲明一個委托 委托使用關鍵字delegate,從外形上看和一個沒有方法體的 ...
委托是個說爛了的話題,但是依舊有好多人不知道為什麼要在C#中使用委托,最近有朋友也問到我這個問題,所以舉例些場景,以供那些知道怎麼聲明委托、怎麼調用卻不知道為什麼要用的朋友一些參考,當然也是希望驗證下自己的理解是否正確。
如何聲明一個委托
委托使用關鍵字delegate,從外形上看和一個沒有方法體的方法一樣,只不過是多了個關鍵字。
public delegate void MyDelegateHandler();//無返回值,無參數
public delegate int MyDelegateHandler1();//有返回值,無參數
public delegate object MyDelegateHandler2(string name);//有返回值,有參數
public delegate object MyDelegateHandler3(object first,ref int second,out float third, params object[] args);//有返回值,多個返回值
委托的聲明可以放在類的外面,也可以在類的內部
public delegate object MyDelegateHandler2(string name);//有返回值,有參數
static class Program
{
public delegate void MyDelegateHandler();//無返回值,無參數
}
C#內置的委托類型
在.NET Framework中定義了大量的委托類型,像什麼WaitCallback、ParameterizedThreadStart、EventHandler、...,為了相容舊版本所以一直沒去掉,從.NET Framework 3.5後你可以使用Action、和Func(帶返回值)來更簡單的使用委托,他們都定義了大量的重載版本
委托的使用
下麵是流年寫的一個計算器類(比較簡陋),裡面有各種運算方法,每個方法只乾一件事,恩,滿足了單一原則,棒棒的!
public class Calculator
{
public static int Add(int first, int second)
{
return first + second;
}
public static int Sub(int first, int second)
{
return first - second;
}
}
流年很簡單的就使用了這個類里的方法,哇,毫無壓力
var result = Calculator.Add(1, 6);
現在問題來了“在做運算前,需要去驗證每個參數(假設這裡希望使用的參數都是正整數)”,╮(╯▽╰)╭每個方法都需要去加一段代碼
public static int Add(int first, int second)
{
if (first <= 0 || second <= 0)
{
throw new ArgumentException("參數錯誤");
}
return first + second;
}
一個方法一個方法的去改,很是麻煩,乾脆我把計算直接寫一個方法里,三下五除二,流年開始啪,啪,啪...
public static int Calc(int first, int second, string operater)
{
if (first <= 0 || second <= 0)
{
throw new ArgumentException("參數錯誤");
}
int result = 0;
if (operater.Equals("+"))
{
result = first + second;
}
if (operater.Equals("-"))
{
result = first - second;
}
return result;
}
OK,搞定,很簡單嘛,但仔細一看,我靠Calc計算方法中幹了那麼多事情,又是加又是減的,如果計算器類還要添加對乘法的支持,還需要再修改這個方法,代碼耦合度太高了,這不就違背了對擴展開放,對修改關閉的原則了嘛。就在這時,天空烏雲密佈,一道閃電擊中了流年的腦袋...
首先我們來看,這段代碼的變化點是什麼?運算方法嘛
有什麼辦法可以隔離這種變化呢?委托嘛
public static int Calc(int first, int second, Func<int, int, int> handler)
{
if (first <= 0 || second <= 0)
{
throw new ArgumentException("參數錯誤");
}
return handler.Invoke(first, second);//更簡單的寫法handler(first, second)
}
Func<int, int, int> calcHandler = new Func<int, int, int>(Calculator.Add);
var result = Calculator.Calc(1, 6, calcHandler);
這樣不用去改變原來的方法,Add還是Add,減法運算還是減法運算,就算再添加乘/除演算法,直接添加加乘/除演算法相關的方法就行,也不用去改動原來的代碼了。而且演算法選擇邏輯也是交給的客戶端,而不是方法內部。將變化隔離了出去。
更簡單的使用委托
上面的委托實例聲明好麻煩,那我們再改進改進,可以將方法直接賦值給委托實例
Func<int, int, int> calcHandler = Calculator.Add;
var result = Calculator.Calc(1, 6, calcHandler);
再改進改進,直接將方法當做實參傳遞
var result = Calculator.Calc(1, 6, Calculator.Add);
此刻,有沒有一種想把委托按在床上的衝動。不要著急,這還只是前戲...
Lambda表達式
在上面我們提到可以將一個方法直接賦值給實例,那麼是不是直接就可以將匿名方法直接賦值給委托實例呢?廢話不說,直接試試就知道了
Func<int, int, int> calcHandler = delegate (int first, int second)
{
return first - second;
};
var result = Calculator.Calc(1, 6, calcHandler);
這就完了?當然沒有,讓我們繼續挑逗匿名方法
既然說,calcHandler 實例的引用是指向匿名方法的,那麼是不是可以直接將匿名方法直接滲入Calculator.Calc方法的參數中
var result = Calculator.Calc(1, 6,delegate (int x, int y){return x - y;});
基於匿名函數,從Visual Studio 2010開始,微軟將匿名函數又升級成了Lambda表達式,代碼是越來越簡潔,連TMD方法都不用創建了,直接將演算法寫在調用上。
var result = Calculator.Calc(1, 6, (a, b) => a * b);
而且為了方便對集合類型的操作,微軟還封裝了大量的Linq擴展方法,這些都是基於委托實現的
int[] numbers = { 11, 4, 3, 89, 5, 10 };
//獲取集合中大於10的數字
var query = numbers.Where(w => w > 10);
提供非同步調用
還是回到原來的代碼,Calculator類中委托調用的地方handler.Invoke(first, second),如果說委托實例對應的方法是個耗時的操作,我想我們誰也不想直接同步調用,讓程式傻傻的死在那裡,至少給用戶一些提示。為了處理這種問題,我們可以直接使用委托的非同步調用
public class Calculator
{
public static int Add(int first, int second)
{
Console.WriteLine($"Add Thread Id {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(500);//模擬耗時的操作
return first + second;
}
public static int Sub(int first, int second)
{
return first - second;
}
public static int Calc(int first, int second, Func<int, int, int> handler)
{
Console.WriteLine($"Calc Thread Id {Thread.CurrentThread.ManagedThreadId}");
if (first <= 0 || second <= 0)
{
throw new ArgumentException("參數錯誤");
}
var ir = handler.BeginInvoke(first, second, null, null);
Console.WriteLine("還在計算當中...");
//等待計算結果
return handler.EndInvoke(ir);
}
}
最後一行代碼handler.EndInvoke(ir)
,作用是等待非同步調用返回結果。他會一直阻塞線程直到非同步調用完成,然後返回計算的結果值。
委托非同步調用方法的返回值為一個IAsyncResult介面,我們可以通過該介面的實例屬性IsCompleted輪詢判斷非同步是否調用完成。
在非同步調用的方法參數中,有一個委托類型AsyncCallback,我們可以將一個函數傳給他,非同步方法執行完的時候會自動的去調用這個方法,這也就是所謂的回調函數。在回調函數中我們就可以幹些別的事情,比如定義個事件將結果傳遞出去
//定義一個計算完成事件
public static event Action<int> OnCalcCompelted;
var ir = handler.BeginInvoke(first, second,
(o) =>
{
var result = handler.EndInvoke(o);
//通過事件通知註冊用戶計算已經完成,並將結果傳遞出去
if (OnCalcCompelted!=null)
{
OnCalcCompelted(result);
}
},
null);
Console.WriteLine("還在計算當中...");
結語:
方法與委托就好比普通類與介面(抽象類)的關係。
編碼過程中委托並不一定是強制使用,他只不過是一種實現方式,在某些場景下比較合適,所以不要糾結於是要調用方法還是要通過委托調用,就像不懂設計模式也可以寫代碼完成功能,但是懂得這些套路之後你的代碼會更加有條理,更具有擴展性,當然逼格也越高。但是,我覺得不用模式套路的代碼逼格更高,誰都看不懂 O(∩_∩)O哈哈~
回到我們的Calculator類,如果需求是Calculator中只要實現加法運算,那TMD的誰還用委托,直接實現一個加法方法就行了,就這麼簡單。
涉及到委托的使用還不僅僅是這些,像什麼事件、表達式樹...每個都可以單獨作為主題來講,而且園子里也有很多講解的文章。流年水平有限,就簡單寫到這裡。