本文主要介紹了C#中委託的相關知識。具有一定的參考價值,下面跟著小編一起來看下吧
委託這個東西不是很好理解,可是工作中又經常用到,你隨處可以看到它的身影,真讓人有一種又愛又恨的感覺,我相信許多人被它所困擾過。
一提到委託,如果你學過C語言,你一定會馬上聯想到函數指標。
什麼是委託?委託是C#中型別安全的,可以訂閱一個或多個具有相同簽名方法的函數指標。委託可以把函數做為參數傳遞,其實際意義便是讓別人代理你的事情。委託可以看做是函數的指標,整數可以用整數變數指向它,對象可以用物件變數指向它,
函數也可以用委託變數指向它。我們可以選擇將委託類型看做只定義了一個方法的介面,而委託的執行個體可以看做是實現了那個介面的一個對象。
使用委託,必須滿足4個條件:
聲明委託類型;
必須有一個方法包含了要執行的代碼;
必須建立一個委託執行個體;
必須調用(invoke)委託執行個體。
委託的申明
聲明委託的方式:delegate 傳回值類型 委託類型名(參數)
委託的申明和介面方法的申明基本上一致,只是在傳回型別關鍵字的前面多了一個delegate關鍵字。還有就是委託一般聲明為public類型,因為它隨時要供別人調用的。
委託的本質也是一個類型。我們聲明一個類可以進行執行個體化,同樣委託也可以進行執行個體化。
有如下四種委託:
//1.無參數無傳回值 public delegate void NoParaNoReturnEventHandler(); //2.有參數無傳回值 public delegate void WithParaNoReturnEventHandler(string name); //3.無參數有傳回值 public delegate string NoParaWithReturnEventHandler(); //4.有參數有傳回值 public delegate string WithParaWithReturnEventHandler(string name);
如果代碼想要執行操作,但不知道操作細節,一般可以使用委託。例如, Thread類之所以知道要在一個新線程裡運行什麼,唯一的原因就是在啟動新線程時,向它提供了一個ThreadStart或ParameterizedThreadStart委託執行個體。
Thread th = new Thread(Test);th.Start();public Thread(ThreadStart start);public delegate void ThreadStart();
ThreadStart是一個無參無傳回值的委託。
static void Test() { Console.WriteLine("線程方法"); }
這個Test方法的函數簽名必須和委託ThreadStart的函數簽名一致。
委託的調用
必須先執行個體化委託,然後再調用。
函數的簽名和委託的簽名必須一致。NoParaNoReturnEventHandler _NoParaNoReturnEventHandler = ConsoleInfo;,編譯器幫我們進行了new,但是不能寫成NoParaNoReturnEventHandler _NoParaNoReturnEventHandler = ConsoleInfo();
因為這樣就成為了函數調用。
#region 無傳回值委託調用 public static void Show() { //執行個體化委託 NoParaNoReturnEventHandler _NoParaNoReturnEventHandler = new NoParaNoReturnEventHandler(ConsoleInfo); //NoParaNoReturnEventHandler _NoParaNoReturnEventHandler = ConsoleInfo; //簡寫 //委託調用 通過Invoke()調用,或者可以直接省略 _NoParaNoReturnEventHandler.Invoke(); //_NoParaNoReturnEventHandler(); } private static void ConsoleInfo() { Console.WriteLine("無參數無傳回值的函數調用"); } #endregion
沒有委託就沒有非同步,非同步正是因為委託的存在。
_NoParaNoReturnEventHandler.BeginInvoke(null,null); //非同步呼叫
為什麼要使用委託
我們完全可以直接調用方法,為什麼還需要通過一個委託來調用呢?委託有什麼意義?
解耦,對修改關閉,對擴充開放。邏輯分離。
你可以把委託理解為函數的父類,或者是一個方法的預留位置。
我們來看下代碼,假設有2個方法,一個說英語,一個說漢語,而這2個方法的函數簽名是一樣的。
public static void SayChinese(string name) { Console.WriteLine("你好," + name); } public static void SayEnglish(string name) { Console.WriteLine("hello," + name); }
那麼我們在外部調用的時候,
MyDelegate.SayChinese("張三"); MyDelegate.SayEnglish("zhangsan");
如果要調用這兩個不同的方法,是不是要寫不同的調用代碼
我們能不能只一個方法調用呢?修改代碼如下:
public static void Say(string name,WithParaNoReturnEventHandler handler) { handler(name); } public static void SayChinese(string name) { Console.WriteLine("你好," + name); } public static void SayEnglish(string name) { Console.WriteLine("hello," + name); }
這樣,只通過一個方法Say來進行調用。
如何調用呢?如下三種調用方式:
WithParaNoReturnEventHandler _WithParaNoReturnEventHandler = new WithParaNoReturnEventHandler(MyDelegate.SayChinese); MyDelegate.Say("張三",_WithParaNoReturnEventHandler); MyDelegate.Say("張三", delegate(string name) { Console.WriteLine("你好," + name); }); //匿名方法 MyDelegate.Say("張三", (name) => { Console.WriteLine("你好," + name); }); //lambda運算式
以上代碼使用了幾種調用方式,這些調用方式都是隨著C#的升級而不斷最佳化的。第一種是C#1.0中就存在的傳統調用方式,第二種是C#2.0中的匿名方法調用方式,所謂匿名方法,就是沒有名字的方法,當方法只調用一次時使用匿名方法最合適不過了。C#3中的lambda運算式。其實泛型委派同樣是被支援的,而.NET 3.5則更進一步,引入了一組名為Func的泛型委派類型,它能擷取多個指定類型的參數,並返回另一個指定類型的值。
lambda運算式
lambda運算式的本質就是一個方法,一個匿名方法。
如果方法體只有一行,無傳回值,還可以去掉大括弧和分號。
MyDelegate.Say("張三", (name) => Console.WriteLine("你好," + name));
如果方法體只有一行,有傳回值,可以去掉大括弧和return。
WithParaWithReturnEventHandler _WithParaWithReturnEventHandler = (name)=>name+",你好";
從.NET3.5開始,基本上不需要我們自己來申明委託了,因為系統有許多內建的委託。
Action和Func委託,分別有16個和17個重載。int表示輸入參數,out代表傳回值,out參數放置在最後。
Action表示無傳回值的委託,Func表示有傳回值的委託。因為方法從大的角度來分類,也分為有傳回值的方法和無傳回值的方法。
也就是說具體調用什麼樣的方法,完全由調用方決定了,就有了更大的靈活性和擴充性。為什麼這麼說,如果我有些時候要先說英語再說漢語,有些事時候要先說漢語再說英語,如果沒有委託,我們會怎麼樣實現?請看如下代碼:
public static void SayEnglishAndChinese(string name) { SayEnglish(name); SayChinese(name); } public static void SayChineseAndEnglish(string name) { SayChinese(name); SayEnglish(name); }
如果又突然要添加一種俄語呢?被呼叫者的代碼又要修改,如此迴圈下去,是不是要抓狂了?隨著不斷添加新語種,代碼會變得越來越複雜,越來越難以維護。這樣的代碼耦合性非常高,是不合理的,也就是出現了所謂的代碼的壞味道,你可以通過設計模式(如觀察者模式等),在不使用委託的情況下來重構代碼,但是實現起來是非常麻煩的,要寫很多更多的代碼...
委託可以傳遞方法,而這些方法可以代表一系列的操作,這些操作都由調用方來決定,就很好擴充了,而且十分靈活。我們不會對已有的方法進行修改,而是只以添加方法的形式去進行擴充。
可能有人又會說,我直接在調用方那裡來一個一個調用我要執行哪些方法一樣可以實現這樣的效果啊?
可你有沒有想過,你要調用的是一系列方法,你根本無法複用這一系列的方法。使用委託就不一樣了,它好比一個方法集合的容器,你可以往裡面增減方法,可以複用的。而且使用委託,你可以延時方法列表的調用,還可以隨時對方法列表進行增減。委託對方法進行了再一次的封裝。
總結:也就是當你只能確定方法的函數簽名,無法確定方法的具體執行時,為了能夠更好的擴充,以類似於注入方法的形式來實現新增的功能,就能體現出委託的價值。
委託和直接調用函數的區別:用委託就可以指向任意的函數,哪怕是之前沒定義的都可以,而不用受限於哪幾種。
多播委託
組合的委託必須是同一個類型,其相當於建立了一個按照組合的順序依次調用的新委派物件。委託的組合一般是給事件用的,用普通委託的時候很少用。
通過+來實現將方法添加到委託執行個體中,-來從委託執行個體中進行方法的移除。
+和-純粹是為了簡化代碼而生的,實際上其調用的分別是Delegate.Combine方法和Delegate.Remove。
如果委託中存在多個帶傳回值的方法,那麼調用委託的傳回值是最後一個方法的傳回值。
public static void MultipleShow() { //多播委託 NoParaWithReturnEventHandler _NoParaWithReturnEventHandler = new NoParaWithReturnEventHandler(GetDateTime); _NoParaWithReturnEventHandler += GetDateTime; Console.WriteLine(_NoParaWithReturnEventHandler()); } public static string GetDateTime() { return string.Format("今天是{0}號。", DateTime.Now.Day.ToString()); }
委託總結:
委託封裝了包含特殊傳回型別和一組參數的行為,類似包含單一方法的介面;
委託型別宣告中所描述的類型簽名決定了哪個方法可用於建立委託執行個體,同時決定了調用的簽名;
為了建立委託執行個體,需要一個方法以及(對於執行個體方法來說)調用方法的目標;
委託執行個體是不易變的,就像String一樣;
每個委託執行個體都包含一個調用列表——一個巨集指令清單;
事件不是委託執行個體——只是成對的add/remove方法(類似於屬性的取值方法/賦值方法)。
常見使用情境:表單傳值、線程啟動時Binder 方法、lambda運算式、非同步等等。
生活中的例子:現在不是大家都在搶火車票嗎,使用雲搶票就相當於使用委託,你可以直接自己買票,也可以託管於雲搶票,自己搶票的話,在快要開槍的時候,你必須時刻重新整理,下單輸驗證碼等等,使用雲搶票的話,你只要放票前,提前輸入搶票資訊,就再也不需要你管了,自動出票,你根本不需要知道雲搶票那邊是怎麼幫你實現搶票的。相同時間和車次可以做成一個委託執行個體,有很多人都通過這個委託執行個體來進行搶票操作。