目錄結構:
1.委托語法
本文會詳細闡述委托的使用,以及實現,想必讀者已經知道了函數編程中的回調機制,C#為回調機制提供了一種簡便語法,這就是委托。
在使用委托之前需要使用delegate關鍵字進行聲明:
比如,
delegate void MyDelegate();
上面定義了一個無參數,無返回的委托。定義好后,然后就可以聲明委托對象。
MyDelegate myDelegate=new MyDelegate(SomeStaticMethod);
上面創建了一個委托對象,這個委托對象關聯了一個方法參數。
C#為這種操作提供了一種糖語法,就是不需要構造委托對象,如下:
MyDelegate myDelegate=SomeStaticMethod;
示例:
class Program { delegate void MyDelegate(); static void Main(string[] args) { MyDelegate myDelegate = Method1;//委托靜態方法Method1 myDelegate += new Program().Method2;//委托實例方法Method2 myDelegate.Invoke();//調用委托鏈上的所有方法 Console.ReadKey(); } static void Method1() { Console.WriteLine("method1 invoked"); } void Method2() { Console.WriteLine("method2 invoked"); } }
2.泛型委托
C#允許泛型委托,目的是保證任何類型的對象都可以以類型安全的方式傳給回調方法。
例如:
public delegate TResult CallMe<TResult,TKey,TValue>(TKey key,TValue value);
委托的每個泛型類型參數都可以標記為協變量和逆變量。
在這里介紹一下泛型類型參數的三種變量:
不變量(invariant):意味着泛型類類型參數不能更改。
逆變量(contravariant):意味着泛型類型參數可以從一個類更改為它的某個派生類,C#中使用in關鍵字標記逆變量形式的泛型類型參數。逆變量泛型類型參數T只能出現在輸入位置,作為參數。
協變量(convariant):意味着泛型類型參數可以從一個類更改為它的某個基類,C#中使用out關鍵字標記協變量形式的泛型類型參數。逆變量泛型類型參數只能出現在輸出位置,作為返回類型。
例如存在以下泛型類型委托:
public delegate TResult MyFunc<in T,out TResult>(T arg);
如果像下面定義一個委托變量:
MyFunc(Object,ArgumentException) fn1=null;
然后就可以將它轉變為其他委托類型變量:
MyFunc(String,Exception) fn2=fn1;//轉化 fn2("");//調用
3.委托鏈
C#中提供了一種委托鏈的操作,通過委托鏈可以關聯多個方法。
C#在實現委托鏈的時候使用了組合設計模式。C#中使用Delegate類的Combine類來完成委托的鏈接,例如:
class Program { delegate Int32 Max(Int32 a, Int32 b); static void Main(string[] args) { Max max1 = new Max(new Program().Method1);//定義委托max1 Max max2 = new Max(new Program().Method2);//定義委托max2 Delegate max = Delegate.Combine(max1, max2);//組合委托鏈 Object obj= max.DynamicInvoke(12,13);//動態調用 Console.WriteLine(obj);//13 Console.ReadLine(); } Int32 Method1(Int32 a, Int32 b) { return a > b ? a : b; } Int32 Method2(Int32 a, Int32 b) { if (a > b) { return a; } return b; } }
上面我們使用Delegate的Combine方法合並為委托鏈,其實Delegate類還提供了許多操作方法,例如:
Delegate.remove(Delegate,Delegate);//從第一個委托鏈中移除第二個委托鏈中的委托 Delegate.DynamicInvoke(Object[]);//調用委托鏈中的所有方法 static Delegate CreateDelegate(Type type,MethodInfo method)//從給定的委托類型中,創建一個靜態方法委托
在C#中還給Delegate重載了+=和-=操作符,可以更方便的操作委托鏈,比如:
Max max1 = new Max(new Program().Method1);//定義委托max1 Max max2 = new Max(new Program().Method2);//定義委托max2 max1 += max2;
4.lambda表達式
Lambda表達式是C#3.0才添加的功能,是為委托添加的糖語法,lambda表達式允許以內聯的方式寫回調方法的代碼,而不需要單獨寫到方法中,例如:
delegate Int32 Max(Int32 a, Int32 b); static void Main(string[] args) { Max m = null; m += (a, b) => { return a > b ? a : b; };//lambda表達式 Int32 res= m(10,11);//調用方法 Console.WriteLine(res); Console.ReadLine(); }
lambda表達式的大致格式為:(參數...)=>{方法主體}
通常我們在寫線程的時候,我們都會定義一個WaitCallback委托,然后再關聯方法,現在我們可以像如下這樣:
ThreadPool.QueueUserWorkItem((obj) => { Console.WriteLine("Thread:" + Thread.CurrentThread.ManagedThreadId); }); Console.ReadLine();
關於使用委托,這里有一個不成文規定,通常若是需要在回調方法寫3行以上的代碼就不用lambda表達式,而是重新定義一個方法,並為其分配名稱。若小於等於3行代碼,就可以使用lambda表達式。
例如:
//創建並初始化一個String數組 String[] names = { "Jeff","Kristin","Aidan","Grant"}; //獲取只含有小寫字母a的名稱 Char charTOFind='a'; names = Array.FindAll(names, (name) => { return name.IndexOf(charTOFind) >= 0; }); //將每個字符串的名稱轉化為大寫 names= Array.ConvertAll(names, (name) => { return name.ToUpper(); }); //打印 Array.ForEach(names,System.Console.WriteLine);
5.揭秘委托
到現在我們已經知道了如何定義委托,以及C#為委托提供的糖語法,接下來我們開始探討一下委托究竟是什么,將如下的代碼編譯為二進制文件,
class Program { delegate Int32 MathAdd(Int32 a, Int32 b); static void Main(string[] args) { MathAdd mathAdd = new MathAdd((Int32 a, Int32 b) => { return a + b; }); Int32 result= mathAdd(3,2); Console.WriteLine(result);//5 Console.ReadKey(); } }
然后我們使用ildasm.exe反編譯應用程序得到il文件,就可以從CLR層面觀察出C#的委托到底是怎么回事了。
通過這張圖片我們可以清晰的觀察出,IL代碼生成了一個靜態內部類MathAdd,然后該類派生於MulticastDelegate,到現在我們清楚了C#的委托本質上就是類。除此之外,該類提供了一個構造器方法,BeginInvoke方法,EndInvoke方法,Invoke方法。
6.類庫中的委托
C#已經預先定義好了豐富的委托類型供開發人員使用,在MSCorLib.dll中,有將近50個委托類型,
例如:
public delegate void TryCode(object userData); public delegate void WaitCallback(object state); public delegate void TimerCallback(object state); public delegate void ContextCallback(object state);
......
上面這些委托都有一個共同點,就是他們都是一樣的,也就是說,只需要定義一個委托就可以了。
.NET Framework支持泛型,C#已經為我們定義好了17個Action委托,其中包含16個泛型委托:
public delegate void Action(); public delegate void Action<T>(T arg); public delegate void Action<T1,T2>(T1 arg1,T2 arg2); public delegate void Action<T1,T2,T3>(T1 arg1,T2 arg2,T3 arg3); ...... public delegate void Action<T1,.....,T16>(T1 arg1,......,T16 arg16);
除了Action委托,C#還為我們定義了Func委托,允許回調方法返回值:
public delegate TResult Func<TResult>(); public delegate TResult Func<T,TResult>(T arg); public delegate TResult Func<T1,T2,TResult>(T1 arg1,T2 arg2); public delegate TResult Func<T1,T2,T3,TResult>(T1 arg1,T2 arg2,T3 arg3); ...... public delegate TResult Func<T1,.....,T16,TResult>(T1 arg1,.....,T16 arg16);
上面列出了C#定義的Action和Func委托,如果涉及到委托最好是使用這些委托類型,而不是重新定義,這樣可以減少程序集的大小。
但是如果涉及到ref或out參數,那么就只有自己定義了。
比如:
delegate void Bar(ref Int32 z);
7.委托和反射
通常情況下,使用委托都應該知道委托的原型,但是反射提供了另一種可能來使用委托。
在MethodInfo中提供了一個createDelegate方法,運行在編譯器不知道委托的所有信息下提前創建委托。
public virtual Delegate CreateDelegate(Type delegateType); public virtual Delegate CreateDelegate(Type delegateType, object target);
在創建好Delegate對象后,就可以通過調用對象的DynamicInvoke方法來調用委托。
public Object DynamicInvoke(params Object[] args);
下面的展示了如何使用反射來調用委托:
class Program { static void Main(string[] args) { //定義一個Func<Int32,Int32,Int32>類型的數據 Type type=typeof(Func<Int32,Int32,Int32>); //獲得Program實例的Max方法 MethodInfo methodInfo = typeof(Program).GetMethod("Max", BindingFlags.Instance|BindingFlags.Public); //創建委托 Delegate d = methodInfo.CreateDelegate(type, new Program()); //調用 Object res = d.DynamicInvoke(13, 12); Console.WriteLine(res);//13 Console.ReadKey(); } public Int32 Max(Int32 a, Int32 b) { return a > b ? a : b; } }