.NET委託解析

委託這個概念其實咱們都很熟悉了,可是在使用的時候不少人仍是沒法去把控它,咱們能夠試想一下,在平時編碼的時候,你是直接按照業務邏輯直接建立類,new出一個對象來進行操做的仍是說有用到委託來更高效的完成一些功能.接下來博主將從委託最淺顯的地方開始入手,中間插入對於委託源碼的解析進行逐步加深鞏固,簡單來講,就是經過實例、概念、源碼來最終經過本文的講解能讓我和閱讀的您對於委託的理解提高一些.主題大概分爲:html

  • 經過實例瞭解委託的概念
  • 委託的回調
  • 委託的深刻(委託鏈 - 合併刪除委託)
  • 委託的源碼解析
  • 泛型委託
  • 委託的進步(語法糖,協變和逆變,匿名,閉包)
  • 委託鏈、泛型委託源碼解析
  • 委託和反射
  • 異步委託

【一】經過實例瞭解委託的概念

咱們要學習委託,首要要了解委託的概念,什麼是委託?C#中委託是如何定義的?這些基礎性的知識瞭解以後咱們在深刻的去了解它, 在C#中,委託是一種類型,屬於引用類型,委託的關鍵字是delegate,委託的定義和類的定義同樣,因此凡是能定義類的地方也是能夠定義委託的,public delegate void MyDelegate();這個定義了一個無返回值,無參的委託類型,那麼下面咱們來經過委託編寫一段代碼:
實例 1 : 委託的基本組成
 1 class Program  2 {  3     public delegate void MyDelegate();  4     static void Main(string[] args)  5  {  6         MyDelegate myMessage = new MyDelegate(MyMethod);  7  myMessage();  8  Console.ReadLine();  9  } 10     public static void MyMethod() 11  { 12         Console.WriteLine("我是經過委託調用的"); 13  } 14 }

上述的代碼是能夠直接進行運行的,在上述代碼中,首先咱們聲明瞭一個委託 MyDelegate, 它是無返回值,無參數的 ,同時咱們還建立了一個方法MyMethod(), 這個方法也是 無返回值,無參數的。那麼接下來咱們在看一下主函數,在主函數中,咱們建立了一個委託對象 myMessage  (委託是一種類型,屬於引用類型), 而後在 new的時候咱們能夠看一下它要求的 "參數" 是什麼. 如圖  : 安全

咱們能夠看到 在建立 MyDelegate 的對象時,要求傳入一個 void() target  這個意思就是 無參,無返回值的一個目標函數 (這個咱們後面還會用到,它的含義不只僅如此),最後咱們在調用這個委託對象(詳情請看後面的源碼解析).

【二】委託回調靜態方法和實例方法

委託回調靜態方法和實例方法的區別:
在實例 1 中,咱們給委託傳入的是一個靜態的方法,在此順便簡單說一下靜態方法和實例方法的區別 「靜態方法都是經過關鍵字static來定義的,靜態方法不須要實例這個對象就能夠經過類名來訪問這個對象。在靜態方法中不能直接訪問類中的非靜態成員。而用實例方法則須要經過具體的實例對象來調用,而且能夠訪問實例對象中的任何成員」, 咱們來經過一個實例來了解
 1 public delegate void MyPersonDelegate(string name);  2 static void Main(string[] args)  3 {  4     MyPersonDelegate personDelegate = new MyPersonDelegate(Person.GetPersonName);  5     personDelegate("Static");  6     MyPersonDelegate personIntanceDelegate = new MyPersonDelegate(new PersonIntance().GetPersonName);  7     personIntanceDelegate("Intance");  8 }  9 class Person 10 { 11     public static void GetPersonName(string age) 12  { 13  Console.WriteLine(age); 14  } 15 } 16 class PersonIntance 17 { 18     public void GetPersonName(string name) 19  { 20  Console.WriteLine(name); 21  } 22 }

在上述代碼中,首先咱們定義了一個委託MyPersonDelegate,它是無返回值,而且須要一個string類型的參數類型(在這裏說一點,委託是能夠進行協變和逆變的,具體請參考.NET可變性解析(協變和逆變)),而後咱們分別定義了兩個類personPersonInstance 其中Person中聲明瞭一個GetPersonNam的靜態方法,PersonIntance類中聲明瞭一個GetPersonName的實例方法,在主函數Main中,咱們分別進行調用.在執行的時候,咱們會發現委託的實例後跟一個參數,這個參數其實就是方法的參數,由於咱們所定義的委託要求的是一個執行一個無返回值,有一個string類型的參數的方法,在執行委託的時候,我故意多寫了一個Invoke()這個方法,這裏主要是能夠先熟悉一下Invoke,由於接下來會涉及到它的一些知識點,Invoke也是調用委託的一種方法它和直接經過委託實例執行是同樣的.那麼對於回調靜態方法和回調實例方法而言,委託的內部發生了什麼?咱們能夠經過源碼解析的方法來查看(在下面的段落描述).閉包

【三】委託深刻(委託鏈 - 合併刪除委託)

在討論委託鏈以前,咱們先熟悉一下委託的合併和刪除(這樣可能更好理解一些),在Delegate類型下有兩個靜態的方法Combine和Remove (接下來的源碼解析的會一一的講解),Combine負責將兩個委託實例的調用列表鏈接到一塊兒,而Remove負責從一個委託實例中刪除另外一個實例的調用列表,下面咱們經過一個實例來展現一下委託的合併和刪除異步

實例 3 : 委託的合併和刪除(Combine,Remove)ide

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(),將這兩個委託實例合併到了一塊兒,而後輸出,結果爲 :函數

這就是將兩個委託合併爲了一個委託,並未咱們在看一下更加簡單的寫法.學習

//var dele = (MyPersonDelegate)Delegate.Combine(personDelegate, personIntanceDelegate);
var dele = personDelegate += personIntanceDelegate; dele.Invoke("Albin");

咱們將Combine的方式改成+= 效果和Combine是同樣的.(下面將有源碼解析),熟悉事件的話,咱們能夠發現其實這個是事件加載是同樣的.ui

委託的刪除this

在上面咱們介紹了委託的合併,那麼有合併就會有刪除,在委託裏有一個靜態方法Remove,它用來將合併以後的委託進行移除,它要求的參數爲 Delegate.Remove(source,value);這裏指出要求一個委託的調用列表,以及提供委託移除source的調用列表,如圖 :編碼

實例 3 : 委託的Remove

    var deleRemove = (MyPersonDelegate)Delegate.Remove(personIntanceDelegate,dele); deleRemove.Invoke("Albin");

經過以前的Combine,這段代碼並不難理解,這裏就很少贅說了,接下來是它的簡易寫法

 var deleRemove = personIntanceDelegate -= dele; deleRemove.Invoke("ALbin");

最後兩個的輸出值都爲 personIntanceDelegate的值

【四】委託的源碼解析(反編譯查看委託回調靜態與實例的區別,以及委託鏈的本質)

 接下來咱們對前面所提到的委託回調和委託鏈進行反編譯,查看委託在調用的時候內部是如何實行的,先貼出委託的部分源碼 : 

 1 public abstract class Delegate : ICloneable, ISerializable  2  {  3  [ForceTokenStabilization, SecurityCritical]  4         internal object _target;  5  [SecurityCritical]  6         internal object _methodBase;  7  [ForceTokenStabilization, SecurityCritical]  8         internal IntPtr _methodPtr;  9  [ForceTokenStabilization, SecurityCritical] 10         internal IntPtr _methodPtrAux; 11         /// <summary>Gets the method represented by the delegate.</summary>
12         /// <returns>A <see cref="T:System.Reflection.MethodInfo" /> describing the method represented by the delegate.</returns>
13         /// <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>
14         /// <filterpriority>2</filterpriority>
15  [__DynamicallyInvokable] 16         public MethodInfo Method 17  { 18             [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] 19             get
20  { 21                 return this.GetMethodImpl(); 22  } 23  } 24         /// <summary>Gets the class instance on which the current delegate invokes the instance method.</summary>
25         /// <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>
26         /// <filterpriority>2</filterpriority>
27  [__DynamicallyInvokable] 28         public object Target 29  { 30             [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] 31             get
32  { 33                 return this.GetTarget(); 34  } 35  } 36         /// <summary>Initializes a delegate that invokes the specified instance method on the specified class instance.</summary>
37         /// <param name="target">The class instance on which the delegate invokes <paramref name="method" />. </param>
38         /// <param name="method">The name of the instance method that the delegate represents. </param>
39         /// <exception cref="T:System.ArgumentNullException">
40         ///   <paramref name="target" /> is null.-or- <paramref name="method" /> is null. </exception>
41         /// <exception cref="T:System.ArgumentException">There was an error binding to the target method.</exception>
42  [SecuritySafeCritical] 43         protected Delegate(object target, string method) 44  { 45             if (target == null) 46  { 47                 throw new ArgumentNullException("target"); 48  } 49             if (method == null) 50  { 51                 throw new ArgumentNullException("method"); 52  } 53             if (!this.BindToMethodName(target, (RuntimeType)target.GetType(), method, (DelegateBindingFlags)10)) 54  { 55                 throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTargMeth")); 56  } 57  } 58         /// <summary>Initializes a delegate that invokes the specified static method from the specified class.</summary>
59         /// <param name="target">The <see cref="T:System.Type" /> representing the class that defines <paramref name="method" />. </param>
60         /// <param name="method">The name of the static method that the delegate represents. </param>
61         /// <exception cref="T:System.ArgumentNullException">
62         ///   <paramref name="target" /> is null.-or- <paramref name="method" /> is null. </exception>
63         /// <exception cref="T:System.ArgumentException">
64         ///   <paramref name="target" /> is not a RuntimeType. See Runtime Types in Reflection.-or-<paramref name="target" /> represents an open generic type.</exception>
65  [SecuritySafeCritical] 66         protected Delegate(Type target, string method) 67  { 68             if (target == null) 69  { 70                 throw new ArgumentNullException("target"); 71  } 72             if (target.IsGenericType && target.ContainsGenericParameters) 73  { 74                 throw new ArgumentException(Environment.GetResourceString("Arg_UnboundGenParam"), "target"); 75  } 76             if (method == null) 77  { 78                 throw new ArgumentNullException("method"); 79  } 80             RuntimeType runtimeType = target as RuntimeType; 81             if (runtimeType == null) 82  { 83                 throw new ArgumentException(Environment.GetResourceString("Argument_MustBeRuntimeType"), "target"); 84  } 85             this.BindToMethodName(null, runtimeType, method, (DelegateBindingFlags)37); 86         }
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的做用.

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

    // 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

    // 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 , 匿名方法類簡化委託的代碼.

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: 經過閉包實現

    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; 閉包自己就很差理解, 這個能夠專門拿出一個文章來說解它.在這裏就不深究了.

【七】委託鏈,泛型委託源碼解析

委託鏈/多播委託/合併刪除委託源碼解析

[__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來串聯兩個委託的調用列表

刪除委託

/// <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方法內部實現:

/// <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,能夠進行協變.

由於這篇文章篇幅已經較長,對於異步委託在下篇文章中進行解析.委託和反射留在反射的文章中進行解析.另外但願本文可以幫助到你瞭解到更多或者更深.

若是你以爲本文對你有幫助的話,請點右下角的推薦,或者直接關注我,後續將不斷更新.NET解析這一系列的文章....

.NET解析系列目錄 

【劉彬版權全部,如轉載請註明出處.】

相關文章
相關標籤/搜索