委託這個概念其實我們都很熟悉了,但是在使用的時候很多人還是無法去把控它,我們可以試想一下,在平時編碼的時候,你是直接按照業務邏輯直接建立類,new出一個物件來進行操作的還是說有用到委託來更高效的完成一些功能.接下來博主將從委託最淺顯的地方開始入手,中間插入對於委託原始碼的解析進行逐步加深鞏固,簡單來說,就是通過例項、概念、原始碼來最終通過本文的講解能讓我和閱讀的您對於委託的理解提升一些.主題大概分為:
- 通過例項瞭解委託的概念
- 委託的回撥
- 委託的深入(委託鏈 – 合併刪除委託)
- 委託的原始碼解析
- 泛型委託
- 委託的進步(語法糖,協變和逆變,匿名,閉包)
- 委託鏈、泛型委託原始碼解析
- 委託和反射
- 非同步委託
【一】通過例項瞭解委託的概念
我們要學習委託,首要要了解委託的概念,什麼是委託?C#中委託是如何定義的?這些基礎性的知識瞭解之後我們在深入的去了解它, 在C#中,委託是一種型別,屬於引用型別,委託的關鍵字是delegate,委託的定義和類的定義一樣,所以凡是能定義類的地方也是可以定義委託的,public delegate void MyDelegate();這個定義了一個無返回值,無參的委託型別,那麼下面我們來通過委託編寫一段程式碼:
例項 1 : 委託的基本組成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Program { public delegate void MyDelegate(); static void Main(string[] args) { MyDelegate myMessage = new MyDelegate(MyMethod); myMessage(); Console.ReadLine(); } public static void MyMethod() { Console.WriteLine("我是通過委託呼叫的"); } } |
上述的程式碼是可以直接進行執行的,在上述程式碼中,首先我們宣告瞭一個委託 MyDelegate, 它是無返回值,無引數的 ,同時我們還建立了一個方法MyMethod(), 這個方法也是 無返回值,無引數的。那麼接下來我們在看一下主函式,在主函式中,我們建立了一個委託物件 myMessage (委託是一種型別,屬於引用型別), 然後在 new的時候我們可以看一下它要求的 “引數” 是什麼. 如圖 :
我們可以看到 在建立 MyDelegate 的物件時,要求傳入一個 void() target 這個意思就是 無參,無返回值的一個目標函式 (這個我們後面還會用到,它的含義不僅僅如此),最後我們在呼叫這個委託物件(詳情請看後面的原始碼解析).
【二】委託回撥靜態方法和例項方法
委託回撥靜態方法和例項方法的區別:
在例項 1 中,我們給委託傳入的是一個靜態的方法,在此順便簡單說一下靜態方法和例項方法的區別 “靜態方法都是通過關鍵字static來定義的,靜態方法不需要例項這個物件就可以通過類名來訪問這個物件。在靜態方法中不能直接訪問類中的非靜態成員。而用例項方法則需要通過具體的例項物件來呼叫,並且可以訪問例項物件中的任何成員”, 我們來通過一個例項來了解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public delegate void MyPersonDelegate(string name); static void Main(string[] args) { MyPersonDelegate personDelegate = new MyPersonDelegate(Person.GetPersonName); personDelegate("Static"); MyPersonDelegate personIntanceDelegate = new MyPersonDelegate(new PersonIntance().GetPersonName); personIntanceDelegate("Intance"); } class Person { public static void GetPersonName(string age) { Console.WriteLine(age); } } class PersonIntance { public void GetPersonName(string name) { Console.WriteLine(name); } } |
在上述程式碼中,首先我們定義了一個委託MyPersonDelegate,它是無返回值,並且需要一個string型別的引數型別(在這裡說一點,委託是可以進行協變和逆變的,具體請參考.NET可變性解析(協變和逆變)),然後我們分別定義了兩個類person和PersonInstance 其中Person中宣告瞭一個GetPersonNam的靜態方法,PersonIntance類中宣告瞭一個GetPersonName的例項方法,在主函式Main中,我們分別進行呼叫.在執行的時候,我們會發現委託的例項後跟一個引數,這個引數其實就是方法的引數,因為我們所定義的委託要求的是一個執行一個無返回值,有一個string型別的引數的方法,在執行委託的時候,我故意多寫了一個Invoke()這個方法,這裡主要是可以先熟悉一下Invoke,因為接下來會涉及到它的一些知識點,Invoke也是呼叫委託的一種方法它和直接通過委託例項執行是一樣的.那麼對於回撥靜態方法和回撥例項方法而言,委託的內部發生了什麼?我們可以通過原始碼解析的方法來檢視(在下面的段落描述).
【三】委託深入(委託鏈 – 合併刪除委託)
在討論委託鏈之前,我們先熟悉一下委託的合併和刪除(這樣可能更好理解一些),在Delegate型別下有兩個靜態的方法Combine和Remove (接下來的原始碼解析的會一一的講解),Combine負責將兩個委託例項的呼叫列表連線到一起,而Remove負責從一個委託例項中刪除另一個例項的呼叫列表,下面我們通過一個例項來展示一下委託的合併和刪除
例項 3 : 委託的合併和刪除(Combine,Remove)
1 2 3 4 5 6 7 8 9 |
MyPersonDelegate personDelegate = new MyPersonDelegate(Person.GetPersonName); // 委託例項1 MyPersonDelegate personIntanceDelegate = new MyPersonDelegate(new PersonIntance().GetPersonName); // 委託例項2 var dele = (MyPersonDelegate)Delegate.Combine(personDelegate, personIntanceDelegate); // 通過Combine合併兩個委託例項,得到一個新的委託例項 dele.Invoke("Albin"); // 輸出合併之後的委託例項 Console.Readline(); |
在上述的程式碼中,首先我們定義了兩個委託的例項 personIntanceDelegate , personIntanceDelegate 接下來的一個段程式碼 我們看到 Delegate.Combine(),將這兩個委託例項合併到了一起,然後輸出,結果為 :
這就是將兩個委託合併為了一個委託,並未我們在看一下更加簡單的寫法.
1 2 3 |
//var dele = (MyPersonDelegate)Delegate.Combine(personDelegate, personIntanceDelegate); var dele = personDelegate += personIntanceDelegate; dele.Invoke("Albin"); |
我們將Combine的方式改為+= 效果和Combine是一樣的.(下面將有原始碼解析),熟悉事件的話,我們可以發現其實這個是事件載入是一樣的.
委託的刪除
在上面我們介紹了委託的合併,那麼有合併就會有刪除,在委託裡有一個靜態方法Remove,它用來將合併之後的委託進行移除,它要求的引數為 Delegate.Remove(source,value);這裡指出要求一個委託的呼叫列表,以及提供委託移除source的呼叫列表,如圖 :
例項 3 : 委託的Remove
1 2 |
var deleRemove = (MyPersonDelegate)Delegate.Remove(personIntanceDelegate,dele); deleRemove.Invoke("Albin"); |
通過之前的Combine,這段程式碼並不難理解,這裡就不多贅說了,接下來是它的簡易寫法
1 2 |
var deleRemove = personIntanceDelegate -= dele; deleRemove.Invoke("ALbin"); |
最後兩個的輸出值都為 personIntanceDelegate的值
【四】委託的原始碼解析(反編譯檢視委託回撥靜態與例項的區別,以及委託鏈的本質)
接下來我們對前面所提到的委託回撥和委託鏈進行反編譯,檢視委託在呼叫的時候內部是如何實行的,先貼出委託的部分原始碼 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
public abstract class Delegate : ICloneable, ISerializable { [ForceTokenStabilization, SecurityCritical] internal object _target; [SecurityCritical] internal object _methodBase; [ForceTokenStabilization, SecurityCritical] internal IntPtr _methodPtr; [ForceTokenStabilization, SecurityCritical] internal IntPtr _methodPtrAux; /// <summary>Gets the method represented by the delegate.</summary> /// <returns>A <see cref="T:System.Reflection.MethodInfo" /> describing the method represented by the delegate.</returns> /// <exception cref="T:System.MemberAccessException">The caller does not have access to the method represented by the delegate (for example, if the method is private). </exception> /// <filterpriority>2</filterpriority> [__DynamicallyInvokable] public MethodInfo Method { [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get { return this.GetMethodImpl(); } } /// <summary>Gets the class instance on which the current delegate invokes the instance method.</summary> /// <returns>The object on which the current delegate invokes the instance method, if the delegate represents an instance method; null if the delegate represents a static method.</returns> /// <filterpriority>2</filterpriority> [__DynamicallyInvokable] public object Target { [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get { return this.GetTarget(); } } /// <summary>Initializes a delegate that invokes the specified instance method on the specified class instance.</summary> /// <param name="target">The class instance on which the delegate invokes <paramref name="method" />. </param> /// <param name="method">The name of the instance method that the delegate represents. </param> /// <exception cref="T:System.ArgumentNullException"> /// <paramref name="target" /> is null.-or- <paramref name="method" /> is null. </exception> /// <exception cref="T:System.ArgumentException">There was an error binding to the target method.</exception> [SecuritySafeCritical] protected Delegate(object target, string method) { if (target == null) { throw new ArgumentNullException("target"); } if (method == null) { throw new ArgumentNullException("method"); } if (!this.BindToMethodName(target, (RuntimeType)target.GetType(), method, (DelegateBindingFlags)10)) { throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTargMeth")); } } /// <summary>Initializes a delegate that invokes the specified static method from the specified class.</summary> /// <param name="target">The <see cref="T:System.Type" /> representing the class that defines <paramref name="method" />. </param> /// <param name="method">The name of the static method that the delegate represents. </param> /// <exception cref="T:System.ArgumentNullException"> /// <paramref name="target" /> is null.-or- <paramref name="method" /> is null. </exception> /// <exception cref="T:System.ArgumentException"> /// <paramref name="target" /> is not a RuntimeType. See Runtime Types in Reflection.-or-<paramref name="target" /> represents an open generic type.</exception> [SecuritySafeCritical] protected Delegate(Type target, string method) { if (target == null) { throw new ArgumentNullException("target"); } if (target.IsGenericType && target.ContainsGenericParameters) { throw new ArgumentException(Environment.GetResourceString("Arg_UnboundGenParam"), "target"); } if (method == null) { throw new ArgumentNullException("method"); } RuntimeType runtimeType = target as RuntimeType; if (runtimeType == null) { throw new ArgumentException(Environment.GetResourceString("Argument_MustBeRuntimeType"), "target"); } this.BindToMethodName(null, runtimeType, method, (DelegateBindingFlags)37); } Delegate部分原始碼 |
在上述的原始碼中,我們看到Delegate有四個私有欄位,分別為:object _target;object _methodBase;IntPtr _methodPtr;IntPtr _methodPtrAux;
_target: 返回的是一個引用型別,它是用來表示引用(回撥)的方法如果是靜態的方法 _target返回Null,如果回撥的是例項物件則返回該方法的引用地址,在往下看有一個Target的屬性,這個屬性對應的就是_target這個欄位,另外Target返回的是this.GetTarget(),通過註釋 ” The object on which the current delegate invokes the instance method, if the delegate represents an instance method; null if the delegate represents a static method. “也證實了_target的作用.
1 2 3 4 5 6 7 8 |
internal virtual object GetTarget() { if (!this._methodPtrAux.IsNull()) { return null; } return this._target; } |
我們檢視GetTarget這個屬性之後,發現這裡面有一個欄位 _methodPtrAux ,同時我們在看一下 MethodInfo GetMethodImpl() 這個方法描述為 :The caller does not have access to the method represented by the delegate (for example, if the method is private). 如果委託指向的是例項方法,則_methodPtrAux就是0 ,如果委託指向的是靜態方法,則這時_methodPtrAux起的作用與_mthodPtr在委託指向例項方法的時候是一樣的.
_methodPtr 是指向該方法的指標.
_methodBase 是給委託賦值時傳遞的方法
【五】泛型委託
泛型委託主要為我們解決定義委託的數量比較多,在.NET FreamWork 支援泛型之後,我們就可以用泛型的方式來定義委託,首先泛型的好處其中之一就是減少複雜性,提高可重用性(詳細請參考.NET泛型解析(上)),下面我們通過例項來了解一下泛型委託的魅力.
例項 4 : 泛型委託之Action
1 2 3 4 |
// Action例項 Action<string> action = new Action<string>(Person.GetPersonName); action.Invoke("Albin"); Console.ReadLine(); |
在上述程式碼中,我們建立了一個泛型委託 action, 並且我們在建立的時候可以看到Action<> 它要求的是一個委託的引數型別,並且在建立例項的時候和我們例項1一樣要求一個void (string)Target, 無返回值,有一個引數. 並且引數型別指定為了 in,說明它是可以逆變的(.NET可變性解析(協變和逆變));
.NET FreamWork為我們提供了17個Action委託,它們從無引數到最多16個引數,這個完全夠我們用了(除非你的委託要傳16以上的引數,那麼只有自己定義了) , 其中注意一點 : Action給我們提供的是隻有引數而不帶返回值的委託,那麼如果我們要傳遞帶有返回值和引數的呢? 這時,.NET FreamWork也考慮到了這一點,它為我們提供了另外一個函式 Func,它和Action一樣提供了17個引數另加一個返回值型別,當第一次使用它們的時候,感覺整天天空都是藍藍的…簡直太帥了.
下面我們通過一個例項來了解一下Func函式
例項 5 : 泛型委託之Func
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Func 例項 Func<string, string> func = new Func<string, string>(Person.GetName); var result = func.Invoke("This is Arg"); Console.WriteLine(result); Console.ReadLine(); class Person { public static string GetName(string name) { return name; } } |
在上述的程式碼中,我們建立了一個Func的例項,要求func所要回撥的方法有一個string型別的返回值,並且有一個string型別的引數,所以我們在Person類中定義了一個 GetName的方法,在func.Invoke(“”),呼叫的時候,它所返回值的型別為GetName所返回的型別.最後輸出結果為 :
泛型委託的好處:
在平時的開發過程中,我們應儘量使用泛型委託的方式來使用委託,避免使用自定義的委託
第一 : 可以減少我們委託的定義數量
第二 : 泛型是型別安全的
第三 : 方便進行協變和逆變
第四 : 簡化程式碼
【六】委託的進步(語法糖,協變和逆變,匿名,閉包)
C#語法糖 : 所謂語法糖是在C#程式碼中,簡化程式碼量、是程式碼編寫的更加優美,所以稱之為語法糖.
匿名函式 : 在C#2.0中引入的匿名函式,所謂匿名函式就是沒有實際方法宣告的委託例項,它們是直接內嵌在程式碼中的
Lambda : 在C#3.0中引入的Lambda表示式,它比匿名方法更加的簡潔
在這裡不會過深的去描述Lambda和匿名這一塊,因為過幾天會編寫關於 《.NET解析之Lambda和匿名的內部機制實現》 方面的文章.在這裡我們只需要知道就可以了.
例項 6 : 通過Lambda , 匿名方法類簡化委託的程式碼.
1 2 3 4 5 6 7 |
MyPersonDelegate personDelegate = p => Console.WriteLine(p.ToString()); personDelegate.Invoke("無返回值,有引數"); MyDelegate myDelegate = () => Console.WriteLine("無參,無返回值"); myDelegate(); MyPersonDelegateStr delegateStr = p => { return p; }; Console.WriteLine(delegateStr.Invoke("有引數,有返回值")); Console.ReadLine(); |
例項 7: 通過閉包實現
1 2 3 4 5 6 7 8 9 10 11 12 |
var f = Func(); Console.WriteLine(f()); Console.ReadLine(); public static Func<int> Func() { var i = 10; return () => { return i; }; } |
上述的程式碼我們可以反編譯看一下 :
可以看出來return返回的是一個匿名委託,因為Func它是要求必須有一個返回值的,從中返回的一個匿名的委託物件,在匿名委託中,我加了一個Console.WriteLine(i); 在例項的中的程式碼中是沒有的, 這一點主要是因為 能體現出一個方法體來,如果按照我們例項的寫法反編譯出來直接就是 return () => i; 閉包本身就不好理解, 這個可以專門拿出一個文章來講解它.在這裡就不深究了.
【七】委託鏈,泛型委託原始碼解析
委託鏈/多播委託/合併刪除委託原始碼解析
1 2 3 4 5 6 7 8 9 |
[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] public static Delegate Combine(Delegate a, Delegate b) { if (a == null) { return b; } return a.CombineImpl(b); } |
上述程式碼為 Combine的內部實現,我們可以看到a為null則引用了一個空的方法例項,直接返回另一個委託物件,通過CombineImpl來串聯兩個委託的呼叫列表
刪除委託
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/// <summary>Removes the last occurrence of the invocation list of a delegate from the invocation list of another delegate.</summary> /// <returns>A new delegate with an invocation list formed by taking the invocation list of <paramref name="source" /> and removing the last occurrence of the invocation list of <paramref name="value" />, if the invocation list of <paramref name="value" /> is found within the invocation list of <paramref name="source" />. Returns <paramref name="source" /> if <paramref name="value" /> is null or if the invocation list of <paramref name="value" /> is not found within the invocation list of <paramref name="source" />. Returns a null reference if the invocation list of <paramref name="value" /> is equal to the invocation list of <paramref name="source" /> or if <paramref name="source" /> is a null reference.</returns> /// <param name="source">The delegate from which to remove the invocation list of <paramref name="value" />. </param> /// <param name="value">The delegate that supplies the invocation list to remove from the invocation list of <paramref name="source" />. </param> /// <exception cref="T:System.MemberAccessException">The caller does not have access to the method represented by the delegate (for example, if the method is private). </exception> /// <exception cref="T:System.ArgumentException">The delegate types do not match.</exception> /// <filterpriority>1</filterpriority> [__DynamicallyInvokable, SecuritySafeCritical] public static Delegate Remove(Delegate source, Delegate value) { if (source == null) { return null; } if (value == null) { return source; } if (!Delegate.InternalEqualTypes(source, value)) { throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTypeMis")); } return source.RemoveImpl(value); } |
上述程式碼為Remove的內部實現,從另一個委託的呼叫列表中移除委託的呼叫列表的最後一個匹配的項,通過RemoveImpl方法移除,RemoveImpl方法內部實現:
1 2 3 4 5 6 7 8 9 10 11 12 |
/// <summary>Removes the invocation list of a delegate from the invocation list of another delegate.</summary> /// <returns>A new delegate with an invocation list formed by taking the invocation list of the current delegate and removing the invocation list of <paramref name="value" />, if the invocation list of <paramref name="value" /> is found within the current delegate's invocation list. Returns the current delegate if <paramref name="value" /> is null or if the invocation list of <paramref name="value" /> is not found within the current delegate's invocation list. Returns null if the invocation list of <paramref name="value" /> is equal to the current delegate's invocation list.</returns> /// <param name="d">The delegate that supplies the invocation list to remove from the invocation list of the current delegate. </param> /// <exception cref="T:System.MemberAccessException">The caller does not have access to the method represented by the delegate (for example, if the method is private). </exception> protected virtual Delegate RemoveImpl(Delegate d) { if (!d.Equals(this)) { return this; } return null; } |
source.RemoveImpl(value); source將從中移除 value 的呼叫列表, value提供將從其中移除 source 的呼叫列表的呼叫列表.
一個新委託,其呼叫列表的構成方法為:獲取 source 的呼叫列表,如果在 source 的呼叫列表中找到了 value 的呼叫列表,則從中移除 value 的最後一個呼叫列表.
如果 value 為 null,或在 source 的呼叫列表中沒有找到 value 的呼叫列表,則返回 source.如果 value 的呼叫列表等於 source 的呼叫列表,或 source 為空引用,則返回空引用.
例如 : 在我們的例項 3中如果將 var deleRemove = (MyPersonDelegate)Delegate.Remove(personIntanceDelegate,dele);將source的值改為dele,將value的值改為personIntanceDelegate,則會返回一個Null.
泛型委託原始碼解析
首先我們來看一下Action的委託:
(檢視大圖)
在這裡我們就拿出一些Action中有8個引數的來舉例了,註釋中標明 : Encapsulates a method that has eight parameters and does not return a value. 意思 : 封裝另一個方法,具有八個引數並且不返回值的方法,在來看一下Action的定義,Action被定義為delegate型別void返回的方法,並且有1-18個指定為in的引數,我們說過了in可以進行逆變.
然後我們在來看Func
(檢視大圖)
因為Func的引數也較多,我們這裡只拿出帶有8個引數的來舉例了,從註釋中我們知道 : Encapsulates a method that has eight parameters and returns a value of the type specified by the ,封裝一個方法,具有八個引數並返回一個值所指定的方法,同時,返回值為out,可以進行協變.
因為這篇文章篇幅已經較長,對於非同步委託在下篇文章中進行解析.委託和反射留在反射的文章中進行解析.另外希望本文能夠幫助到你瞭解到更多或者更深.