.NET委托解析


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

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

【一】通過實例了解委托的概念

我們要學習委托,首要要了解委托的概念,什么是委托?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)

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是一樣的.(下面將有源碼解析),熟悉事件的話,我們可以發現其實這個是事件加載是一樣的.

委托的刪除

在上面我們介紹了委托的合並,那么有合並就會有刪除,在委托里有一個靜態方法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解析系列目錄 

【劉彬版權所有,如轉載請注明出處.】


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM