委托概述 將方法調用者和目標方法動態關聯起來,委托是一個類,所以它和類是同級的,可以通過委托來掉用方法,不要誤以為委托和方法同級的,方法只是類的成員。委托定義了方法的類型(定義委托和與之對應的方法必須具有相同的參數個數,並且類型相同,返回值類型相同),使得可以將方法當作另一個方法的參數來進行傳遞,這 ...
委托概述
將方法調用者和目標方法動態關聯起來,委托是一個類,所以它和類是同級的,可以通過委托來掉用方法,不要誤以為委托和方法同級的,方法只是類的成員。委托定義了方法的類型(定義委托和與之對應的方法必須具有相同的參數個數,並且類型相同,返回值類型相同),使得可以將方法當作另一個方法的參數來進行傳遞,這種將方法動態地賦給參數的做法,可以避免在程式中大量使用If-Else(Switch)語句,同時使得程式具有更好的可擴展性。
基礎委托(Delegate)
在.Net中聲明委托使用關鍵詞delegate,委托具有多種使用方式(以下均為同步委托調用):
1 /// <summary> 2 /// 普通委托基礎調用方式(同步委托) 3 /// </summary> 4 public class Delegates 5 { 6 /// <summary> 7 /// 定義有參無返回值委托 8 /// </summary> 9 /// <param name="i"></param> 10 public delegate void NoReturnWithParameters(string o); 11 /// <summary> 12 /// 構造函數實例化 13 /// </summary> 14 public void DemoOne() 15 { 16 NoReturnWithParameters methord = new NoReturnWithParameters(this.Test); 17 methord.Invoke("One-ok"); 18 } 19 /// <summary> 20 /// 賦值對象 21 /// </summary> 22 public void DemoTwo() 23 { 24 NoReturnWithParameters methord = this.Test; 25 methord.Invoke("Two-ok"); 26 } 27 /// <summary> 28 /// DotNet 2.0 29 /// </summary> 30 public void DemoThree() 31 { 32 NoReturnWithParameters methord = new NoReturnWithParameters( 33 delegate (string o) 34 { 35 Console.WriteLine("有參無返回值:{0}", o); 36 } 37 ); 38 methord.Invoke("Three-ok"); 39 } 40 /// <summary> 41 /// DotNet 3.0 42 /// </summary> 43 public void DemoFour() 44 { 45 NoReturnWithParameters methord = new NoReturnWithParameters( 46 (string o) => 47 { 48 Console.WriteLine("有參無返回值:{0}", o); 49 } 50 ); 51 methord.Invoke("Four-ok"); 52 } 53 /// <summary> 54 /// 委托約束 55 /// </summary> 56 public void DemoFive() 57 { 58 NoReturnWithParameters methord = new NoReturnWithParameters( 59 (o) => 60 { 61 Console.WriteLine("有參無返回值:{0}", o); 62 } 63 ); 64 methord.Invoke("Five-ok"); 65 } 66 /// <summary> 67 /// 方法只有一行去則掉大括弧及分號 68 /// </summary> 69 public void DemoSix() 70 { 71 NoReturnWithParameters methord = new NoReturnWithParameters((o) => Console.WriteLine("有參無返回值:{0}", o)); 72 methord.Invoke("Six-ok"); 73 } 74 public void DemoSeven() 75 { 76 NoReturnWithParameters methord = (o) => Console.WriteLine("有參無返回值:{0}", o); 77 methord.Invoke("Seven-ok"); 78 } 79 /// <summary> 80 /// 定義有參無返回值測試方法 81 /// </summary> 82 /// <param name="o"></param> 83 private void Test(string o) 84 { 85 Console.WriteLine("有參無返回值:{0}", o); 86 } 87 /* 88 * 作者:Jonins 89 * 出處:http://www.cnblogs.com/jonins/ 90 */ 91 }
同步委托&非同步委托
同步委托:委托的Invoke方法用來進行同步調用。同步調用也可以叫阻塞調用,它將阻塞當前線程,然後執行調用,調用完畢後再繼續向下進行。
非同步委托:非同步調用不阻塞線程,而是把調用塞到線程池中,程式主線程或UI線程可以繼續執行。委托的非同步調用通過BeginInvoke和EndInvoke來實現。
以下為非同步委托調用方式:
1 class Program 2 { 3 /// <summary> 4 /// 定義有參無返回值委托 5 /// </summary> 6 /// <param name="i"></param> 7 public delegate void NoReturnWithParameters(string o); 8 static void Main(string[] args) 9 { 10 NoReturnWithParameters methord = new NoReturnWithParameters(Test); 11 Console.WriteLine("主線程執行1"); 12 Console.WriteLine("主線程執行2"); 13 methord.BeginInvoke("demo-ok", null, null); 14 Console.WriteLine("主線程執行3"); 15 Console.WriteLine("主線程執行4"); 16 Console.ReadKey(); 17 } 18 /// <summary> 19 /// 非同步調用委托方法 20 /// </summary> 21 /// <param name="o"></param> 22 static void Test(string o) 23 { 24 Console.WriteLine("有參無返回值:{0}", o); 25 } 26 /* 27 * 作者:Jonins 28 * 出處:http://www.cnblogs.com/jonins/ 29 */ 30 }
因為調用BeginInvoke為非同步委托,不會阻塞主線程,運行結果如下:
非同步回調(Callback)
非同步回調通過設置回調函數,當調用結束時會自動調用回調函數,可以在回調函數里觸發EndInvoke,這樣就釋放掉了線程,可以避免程式一直占用一個線程。
1 class Program 2 { 3 /// <summary> 4 /// 定義有參有返回值委托 5 /// </summary> 6 /// <param name="i"></param> 7 public delegate string ReturnWithParameters(string o); 8 static void Main(string[] args) 9 { 10 ReturnWithParameters methord = new ReturnWithParameters(Test); 11 Console.WriteLine("主線程執行1"); 12 Console.WriteLine("主線程執行2"); 13 /* 14 BeginInvoke方法參數個數不確定, 最後兩個參數含義固定,如果不使用的話,需要賦值null 15 委托的方法無參數,這種情況下BeginInvoke中只有兩個參數。 16 此外,委托的方法有幾個參數,BeginInvoke中從左開始,對應響應的參數。 17 1.倒數第二個參數:是有一個參數值無返回值的委托,它代表的含義為,該線程執行完畢後的回調。 18 2.倒數第一個參數:向即回調中傳值,用AsyncState來接受。 19 3.其它參數:對應委托方法的參數。 20 */ 21 IAsyncResult asyncResult = methord.BeginInvoke("demo-ok", new AsyncCallback(Callback), "AsycState:給回調函數的參數傳遞在此處出傳值"); 22 Console.WriteLine("主線程執行3"); 23 Console.WriteLine("主線程執行4"); 24 Console.ReadKey(); 25 } 26 /// <summary> 27 /// 非同步調用委托方法 28 /// </summary> 29 /// <param name="o"></param> 30 /// <returns></returns> 31 private static string Test(string o) 32 { 33 return "委托方法執行成功:" + o; 34 } 35 /// <summary> 36 /// 回調函數 37 /// </summary> 38 /// <param name="asyncResult"></param> 39 private static void Callback(IAsyncResult asyncResult) 40 { 41 /* 42 *asyncResult為回調前非同步調用方法返回值 43 *AsyncResult 是IAsyncResult介面的一個實現類,引用空間:System.Runtime.Remoting.Messaging 44 *AsyncDelegate 屬性可以強制轉換為定義的委托類型 45 */ 46 ReturnWithParameters methord = (ReturnWithParameters)((System.Runtime.Remoting.Messaging.AsyncResult)asyncResult).AsyncDelegate; 47 Console.WriteLine(methord.EndInvoke(asyncResult)); 48 Console.WriteLine(asyncResult.AsyncState); 49 } 50 /* 51 * 作者:Jonins 52 * 出處:http://www.cnblogs.com/jonins/ 53 */ 54 }
執行結果如下:
註意:
1.非同步調用只能調用一次EndInvoke,否則會報錯。
2.如果不回調函數中執行EndInvoke,請在非同步調用後手動執行EndInvoke方法釋放資源。
非同步委托線程等待
1.【Delegate】.EndInvoke(推薦)
1 public delegate void NoReturnWithParameters(string o); 2 NoReturnWithParameters noReturnWithParameters = new NoReturnWithParameters(...); 3 ...... 4 noReturnWithParameters.EndInvoke(asyncResult);
2.【IAsyncResult】.AsyncWaitHandle.WaitOne(可以定義等待時間,超過等待時間不繼續等待向下執行)
1 IAsyncResult asyncResult = null; 2 asyncResult.AsyncWaitHandle.WaitOne(2000);//等待2000毫秒,超時不等待
3.【IAsyncResult】.IsCompleted(是IAsyncResult對象的一個屬性,該值指示非同步操作是否已完成。不推薦)
1 IAsyncResult asyncResult = xxx.BeginInvoke(...); 2 while (!asyncResult.IsCompleted) 3 { 4 //正在等待中 5 }
內置委托(泛化委托)
.Net Framework 提供兩個支持泛型的內置委托,分別是Action<>和Func<>,在System命名空間中定義,結合lambda表達式,可以提高開發效率。
使用方式如下:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //使用Action聲明委托 6 Action<string> action = TestAction; 7 action.Invoke("action-demo-ok"); 8 //使用Func聲明委托 9 Func<string, string> func = TestFunc; 10 string result = func.Invoke("func-demo-ok"); 11 Console.WriteLine(result); 12 Console.ReadKey(); 13 } 14 private static void TestAction(string o) 15 { 16 Console.WriteLine("TestAction方法執行成功:{0}", o); 17 } 18 private static string TestFunc(string o) 19 { 20 return "TestFunc方法執行成功:" + o; 21 } 22 /* 23 * 作者:Jonins 24 * 出處:http://www.cnblogs.com/jonins/ 25 */ 26 }
Action:無返回值的泛型委托,目前.NET Framework提供了17個Action委托,它們從無參數到最多16個參數。
public delegate void Action | |
Action | 無返回值的泛型委托 |
Action<int,string> | 傳入參數int、string,無返回值的委托 |
Action<int,string,bool> | 傳入參數int,string,bool,無返回值的委托 |
Action<bool,bool,bool,bool> | 傳入4個bool型參數,無返回值的委托 |
Action最少0個參數,最多16個參數,無返回值。 |
Func:有返回值的泛型委托,.NET Framework提供了17個Func函數,允許回調方法返回值。
public delegate TResult Func | |
Func<int> | 無參,返回值為int的委托 |
Func<int,string> | 傳入參數int,返回值為string類型的委托 |
Func<object,string,bool> | 傳入參數為object, string 返回值為bool類型的委托 |
Func<T1,T2,,T3,int> 表示 | 傳入參數為T1,T2,,T3(類型)返回值為int類型的委托 |
Func最少0個參數,最多16個參數,根據返回值泛型返回。必須有返回值,不可為void。 |
本質上Action和Func都為delegate ,在System命名空間中定義(in和out用來標識變數)
除此之外還有Predicate,它是固定返回值為bool類型的泛型委托。Action和Func足夠使用這裡不做介紹。
註意:
1.委托定義不要太多,微軟僅在MSCorLib.dll中就有進50個委托類型,而且.NET Framework現在支持泛型,所以我們只需幾個泛型委托(在System命名空間中定義)就能表示需要獲取多達16個參數的方法。
2.如需獲取16個以上參數,就必須定義自己的委托類型。所以建議儘量使用內置委托,而不是在代碼中定義更多的委托類型,這樣可以減少代碼中的類型數量,同時簡化編碼。
3.如需使用ref或out關鍵字以傳引用的方式傳遞參數,就需要定義自己的委托。
內置委托(泛化委托)參數協變&逆變
協變(out):假定S是B的子類,如果X(S)允許引用轉換成X(B),那麼稱X為協變類。(支持“子類”向“父類”轉換)
逆變(in):假定S是B的子類,如果X(B)允許引用轉換成X(X),那麼稱X為協變類。(支持“父類”向“子類”轉換)
正如泛化介面,泛型委托同樣支持協變與逆變
1 public delegate void Action<in T>(T obj); 2 3 public delegate TResult Func<out TResult>();
Action在System命名空間中定義支持逆變(in)
1 Action<object> x =...; 2 3 Action<string> y = x;
Func在System命名空間中定義支持協變(out)
1 Func<string> x =...; 2 3 Func<object> y = x;
如果要定義一個泛化委托類型,最好按照如下準則:
1.將只用在返回值的類型參數標註為協變(out)
2.將只用在參數的類型參數標註為逆變(in)
委托的相容性
瞭解委托的相容性,更易於在使用委托時使我們構建的代碼具有多態性。
1.類型的相容性:即使簽名相似,委托類也互不相容。
1 delegate void D1(); 2 delegate void D2(); 3 ... 4 D1 d1=Method1; 5 D2 d2=d1;//編譯時錯誤 6 D2 d2=new D2(d1);//這是允許的
如果委托實例執行相同的目標方法,則認為它們是等價的。
1 delegate void D(); 2 ... 3 D1 d1=Method1; 4 D2 d2=Method1; 5 Console.WriteLine(d1==d2);//True
如果多播委托按照相同的順序應用相同的方法責任委托它們是等價的。
2.參數的相容性:當調用一個方法時,可以給方法的參數提供大於其指定類型的變數。這是正常的多態行為。同樣,委托也可以又大於其目標方法參數類型的參數,即逆變。
1 class Program 2 { 3 //委托接受string類型參數 4 delegate void NoReturnWithParameters(string o); 5 static void Main(string[] args) 6 { 7 NoReturnWithParameters noReturnWithParameters = new NoReturnWithParameters(Test); 8 noReturnWithParameters("demo-ok"); 9 Console.ReadKey(); 10 } 11 //目標方法接受object類型參數 12 static void Test(object o) 13 { 14 Console.WriteLine("返回值:{0}", o); 15 } 16 }
上述代碼將參數string在調用目標方法時隱式向上轉換為Object。
3.返回類型的相容性:如果調用一個方法,得到的返回值類型可能大於請求的類型,這是正常多態行為。同樣,委托的返回類型可以小於它的目標方法的返回值類型即協變。
1 class Program 2 { 3 //委托返回object類型 4 delegate object NoReturnWithParameters(string o); 5 static void Main(string[] args) 6 { 7 NoReturnWithParameters noReturnWithParameters = new NoReturnWithParameters(Test); 8 object o = noReturnWithParameters("demo-ok"); 9 Console.WriteLine(o); 10 Console.ReadKey(); 11 } 12 //目標方法返回string類型 13 static string Test(string o) 14 { 15 return "返回值:" + o; 16 } 17 }
註意:標準事件模式的設計宗旨時再其使用公共基類EventArgs時應用逆變。例如,可以用兩個不同的委托調用同一個方法,一個傳遞MouseEventArgs,另一個傳遞KeyEventArgs。
多播委托(+=&-=)
所有的委托的實例都有多播的功能,自定義委托和內置委托都有,可以通過+=和-=給委托增加和刪掉不同的方法,當輸入參數後,每個方法會按順序進行迭代處理,並返回最後一個方法的計算結果。下麵是簡單模擬計算器的一段代碼:1 class Program 2 { 3 public delegate int MulticastInstance(int inputA, int inputB); 4 static void Main(string[] args) 5 { 6 MulticastInstance multicastInstance = Addition; 7 multicastInstance += new MulticastInstance(Reduce); 8 multicastInstance += new MulticastInstance(Multiply); 9 int result = multicastInstance(10, 5); 10 Console.WriteLine("最後執行得到的結果為:{0}", result); 11 Console.ReadKey(); 12 } 13 /// <summary> 14 /// 加法 15 /// </summary> 16 /// <param name="inputA"></param> 17 /// <param name="inputB"></param> 18 /// <returns></returns> 19 private static int Addition(int inputA, int inputB) 20 { 21 int result = inputA + inputB; 22 Console.WriteLine("Addition方法執行結果:{0}", result); 23 return result; 24 } 25 /// <summary> 26 /// 減法 27 /// </summary> 28 /// <param name="inputA"></param> 29 /// <param name="inputB"></param> 30 /// <returns></returns> 31 private static int Reduce(int inputA, int inputB) 32 { 33 int result = inputA - inputB; 34 Console.WriteLine("Reduce方法執行結果:{0}", result); 35 return result; 36 } 37 /// <summary> 38 /// 乘法 39 /// </summary> 40 /// <param name="inputA"></param> 41 /// <param name="inputB"></param> 42 /// <returns></returns> 43 private static int Multiply(int inputA, int inputB) 44 { 45 int result = inputA * inputB; 46 Console.WriteLine("Multiply方法執行結果:{0}", result); 47 return result; 48 } 49 /* 50 * 作者:Jonins 51 * 出處:http://www.cnblogs.com/jonins/ 52 */ 53 }
得到的結果如下:
多播委托本質是:委托是不可變的,因此調用+=或-=的實質是創建一個新的委托實例,並把它賦值給已有變數。所有的委托類型都是從System.MulticastDelegate派生的,它又繼承自System.Delegate,c#將委托中使用的+、-、+=、-=都編譯成System.Delegate的靜態Combine和Remove方法。委托模擬觀察者
能用委托解決的問題,都可以用介面解決。但再下麵的情形中,委托可能是比介面更好的選擇:
1.介面內之定義一個方法
2.需要多播能力
3.訂閱者需要多次實現介面
下麵代碼是委托的觀察者模式,優點是解耦且符合開放封閉原則:
1 public class MulticastDelegates 2 { 3 public delegate int MulticastInstance(int inputA, int inputB); 4 /// <summary> 5 /// 模擬觀察者 6 /// </summary> 7 public void Demo() 8 { 9 Manager manager = new Manager(); 10 manager.Attach(new MulticastInstance(Add)); 11 manager.Attach(new MulticastInstance(Reduce)); 12 manager.Attach(new MulticastInstance(Multiply)); 13 manager.Execute(10, 5); 14 } 15 /// <summary> 16 /// Observer模式、又稱呼發佈訂閱或監聽模式 17 /// </summary> 18 public class Manager 19 { 20 private MulticastInstance Handler; 21 22 /// <summary> 23 /// 附加觀察者 24 /// </summary>