溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

.NET委托解析

發(fā)布時間:2020-04-11 01:24:37 來源:網(wǎng)絡(luò) 閱讀:646 作者:lAlbin 欄目:編程語言

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

      目錄

  • 通過實例了解委托的概念

  • 委托的回調(diào)

  • 委托的深入(委托鏈 - 合并刪除委托)

  • 委托的源碼解析

  • 泛型委托

  • 委托的進(jìn)步(語法糖,匿名,閉包)

  • 委托鏈、泛型委托源碼解析

  • 委托和反射

  • 異步委托

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

我們要學(xué)習(xí)委托,首要要了解委托的概念,什么是委托?C#中委托是如何定義的?這些基礎(chǔ)性的知識了解之后我們在深入的去了解它, 在C#中,委托是一種類型,屬于引用類型,委托的關(guān)鍵字是delegate,委托的定義和類的定義一樣,所以凡是能定義類的地方也是可以定義委托的,public delegate void MyDelegate();這個定義了一個無返回值,無參的委托類型,那么下面我們來通過委托編寫一段代碼:

實例 1 : 委托的基本組成

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("我是通過委托調(diào)用的");
    }
}

上述的代碼是可以直接進(jìn)行運(yùn)行的,在上述代碼中,首先我們聲明了一個委托 MyDelegate, 它是無返回值,無參數(shù)的 ,同時我們還創(chuàng)建了一個方法MyMethod(), 這個方法也是 無返回值,無參數(shù)的。那么接下來我們在看一下主函數(shù),在主函數(shù)中,我們創(chuàng)建了一個委托對象 myMessage  (委托是一種類型,屬于引用類型), 然后在 new的時候我們可以看一下它要求的 "參數(shù)" 是什么. 如圖  : 

.NET委托解析

我們可以看到 在創(chuàng)建 MyDelegate 的對象時,要求傳入一個 void() target  這個意思就是 無參,無返回值的一個目標(biāo)函數(shù) (這個我們后面還會用到,它的含義不僅僅如此),最后我們在調(diào)用這個委托對象(詳情請看后面的源碼解析).

【二】委托回調(diào)靜態(tài)方法和實例方法

委托回調(diào)靜態(tài)方法和實例方法的區(qū)別:

在實例 1 中,我們給委托傳入的是一個靜態(tài)的方法,在此順便簡單說一下靜態(tài)方法和實例方法的區(qū)別 “靜態(tài)方法都是通過關(guān)鍵字static來定義的,靜態(tài)方法不需要實例這個對象就可以通過類名來訪問這個對象。在靜態(tài)方法中不能直接訪問類中的非靜態(tài)成員。而用實例方法則需要通過具體的實例對象來調(diào)用,并且可以訪問實例對象中的任何成員”, 我們來通過一個實例來了解

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類型的參數(shù)類型(在這里說一點,委托是可以進(jìn)行協(xié)變和逆變的,具體請參考.NET可變性解析(協(xié)變和逆變)),然后我們分別定義了兩個類personPersonInstance 其中Person中聲明了一個GetPersonNam的靜態(tài)方法,PersonIntance類中聲明了一個GetPersonName的實例方法,在主函數(shù)Main中,我們分別進(jìn)行調(diào)用.在執(zhí)行的時候,我們會發(fā)現(xiàn)委托的實例后跟一個參數(shù),這個參數(shù)其實就是方法的參數(shù),因為我們所定義的委托要求的是一個執(zhí)行一個無返回值,有一個string類型的參數(shù)的方法,在執(zhí)行委托的時候,我故意多寫了一個Invoke()這個方法,這里主要是可以先熟悉一下Invoke,因為接下來會涉及到它的一些知識點,Invoke也是調(diào)用委托的一種方法它和直接通過委托實例執(zhí)行是一樣的.那么對于回調(diào)靜態(tài)方法和回調(diào)實例方法而言,委托的內(nèi)部發(fā)生了什么?我們可以通過源碼解析的方法來查看(在下面的段落描述).

【三】委托深入(委托鏈 - 合并刪除委托)

在討論委托鏈之前,我們先熟悉一下委托的合并和刪除(這樣可能更好理解一些),在Delegate類型下有兩個靜態(tài)的方法Combine和Remove (接下來的源碼解析的會一一的講解),Combine負(fù)責(zé)將兩個委托實例的調(diào)用列表連接到一起,而Remove負(fù)責(zé)從一個委托實例中刪除另一個實例的調(diào)用列表,下面我們通過一個實例來展示一下委托的合并和刪除

實例 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(),將這兩個委托實例合并到了一起,然后輸出,結(jié)果為 :

.NET委托解析

這就是將兩個委托合并為了一個委托,并未我們在看一下更加簡單的寫法.

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

我們將Combine的方式改為+= 效果和Combine是一樣的.(下面將有源碼解析),熟悉事件的話,我們可以發(fā)現(xiàn)其實這個是事件加載是一樣的.

委托的刪除

在上面我們介紹了委托的合并,那么有合并就會有刪除,在委托里有一個靜態(tài)方法Remove,它用來將合并之后的委托進(jìn)行移除,它要求的參數(shù)為 Delegate.Remove(source,value);這里指出要求一個委托的調(diào)用列表,以及提供委托移除source的調(diào)用列表,如圖 :

.NET委托解析

.NET委托解析

實例 3 : 委托的Remove

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

通過之前的Combine,這段代碼并不難理解,這里就不多贅說了,接下來是它的簡易寫法

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

最后兩個的輸出值都為 personIntanceDelegate的值

【四】委托的源碼解析(反編譯查看委托回調(diào)靜態(tài)與實例的區(qū)別,以及委托鏈的本質(zhì))

 接下來我們對前面所提到的委托回調(diào)和委托鏈進(jìn)行反編譯,查看委托在調(diào)用的時候內(nèi)部是如何實行的,先貼出委托的部分源碼 : 

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 p_w_picpath 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 p_w_picpath 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: 返回的是一個引用類型,它是用來表示引用(回調(diào))的方法如果是靜態(tài)的方法 _target返回Null,如果回調(diào)的是實例對象則返回該方法的引用地址,在往下看有一個Target的屬性,這個屬性對應(yīng)的就是_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這個屬性之后,發(fā)現(xiàn)這里面有一個字段  _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 ,如果委托指向的是靜態(tài)方法,則這時_methodPtrAux起的作用與_mthodPtr在委托指向?qū)嵗椒ǖ臅r候是一樣的.

_methodPtr 是指向該方法的指針.

_methodBase 是給委托賦值時傳遞的方法

【五】泛型委托

 泛型委托主要為我們解決定義委托的數(shù)量比較多,在.NET FreamWork 支持泛型之后,我們就可以用泛型的方式來定義委托,首先泛型的好處其中之一就是減少復(fù)雜性,提高可重用性(詳細(xì)請參考.NET泛型解析(上)),下面我們通過實例來了解一下泛型委托的魅力.

實例 4 : 泛型委托之Action

    // Action實例
    Action<string> action = new Action<string>(Person.GetPersonName);
    action.Invoke("Albin");
    Console.ReadLine();

在上述代碼中,我們創(chuàng)建了一個泛型委托 action, 并且我們在創(chuàng)建的時候可以看到Action<> 它要求的是一個委托的參數(shù)類型,并且在創(chuàng)建實例的時候和我們實例1一樣要求一個void (string)Target, 無返回值,有一個參數(shù). 并且參數(shù)類型指定為了 in,說明它是可以逆變的(.NET可變性解析(協(xié)變和逆變));

.NET委托解析

.NET FreamWork為我們提供了17個Action委托,它們從無參數(shù)到最多16個參數(shù),這個完全夠我們用了(除非你的委托要傳16以上的參數(shù),那么只有自己定義了) , 其中注意一點 : Action給我們提供的是只有參數(shù)而不帶返回值的委托,那么如果我們要傳遞帶有返回值和參數(shù)的呢? 這時,.NET FreamWork也考慮到了這一點,它為我們提供了另外一個函數(shù) Func,它和Action一樣提供了17個參數(shù)另加一個返回值類型,當(dāng)?shù)谝淮问褂盟鼈兊臅r候,感覺整天天空都是藍(lán)藍(lán)的...簡直太帥了.

下面我們通過一個實例來了解一下Func函數(shù)

實例 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;
    }
}

在上述的代碼中,我們創(chuàng)建了一個Func的實例,要求func所要回調(diào)的方法有一個string類型的返回值,并且有一個string類型的參數(shù),所以我們在Person類中定義了一個 GetName的方法,在func.Invoke(""),調(diào)用的時候,它所返回值的類型為GetName所返回的類型.最后輸出結(jié)果為 :

.NET委托解析

泛型委托的好處:

在平時的開發(fā)過程中,我們應(yīng)盡量使用泛型委托的方式來使用委托,避免使用自定義的委托

第一 : 可以減少我們委托的定義數(shù)量

第二 : 泛型是類型安全的

第三 : 方便進(jìn)行協(xié)變和逆變

第四 : 簡化代碼

【六】委托的進(jìn)步(語法糖,匿名,閉包)

C#語法糖 : 所謂語法糖是在C#代碼中,簡化代碼量、是代碼編寫的更加優(yōu)美,所以稱之為語法糖.

匿名函數(shù)  : 在C#2.0中引入的匿名函數(shù),所謂匿名函數(shù)就是沒有實際方法聲明的委托實例,它們是直接內(nèi)嵌在代碼中的

Lambda  :  在C#3.0中引入的Lambda表達(dá)式,它比匿名方法更加的簡潔

在這里不會過深的去描述Lambda和匿名這一塊,因為過幾天會編寫關(guān)于 《.NET解析之Lambda和匿名的內(nèi)部機(jī)制實現(xiàn)》 方面的文章.在這里我們只需要知道就可以了.

實例 6 : 通過Lambda , 匿名方法類簡化委托的代碼.

MyPersonDelegate personDelegate = p => Console.WriteLine(p.ToString());
personDelegate.Invoke("無返回值,有參數(shù)");
MyDelegate myDelegate = () => Console.WriteLine("無參,無返回值");
myDelegate();
MyPersonDelegateStr delegateStr = p => { return p; };
Console.WriteLine(delegateStr.Invoke("有參數(shù),有返回值"));
Console.ReadLine();

實例 7: 通過閉包實現(xiàn)

var f = Func();
    Console.WriteLine(f());
    Console.ReadLine();

    public static Func<int> Func()
    {
        var i = 10;
        return () =>
            {
                return i;
            };
    }

上述的代碼我們可以反編譯看一下 : 

.NET委托解析

可以看出來return返回的是一個匿名委托,因為Func它是要求必須有一個返回值的,從中返回的一個匿名的委托對象,在匿名委托中,我加了一個Console.WriteLine(i); 在實例的中的代碼中是沒有的, 這一點主要是因為 能體現(xiàn)出一個方法體來,如果按照我們實例的寫法反編譯出來直接就是 return () => i; 閉包本身就不好理解, 這個可以專門拿出一個文章來講解它.在這里就不深究了.

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

委托鏈/多播委托/合并刪除委托源碼解析

[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen p_w_picpath boundaries")]
        public static Delegate Combine(Delegate a, Delegate b)
        {
            if (a == null)
            {
                return b;
            }
            return a.CombineImpl(b);
        }

上述代碼為 Combine的內(nèi)部實現(xiàn),我們可以看到a為null則引用了一個空的方法實例,直接返回另一個委托對象,通過CombineImpl來串聯(lián)兩個委托的調(diào)用列表

刪除委托

/// <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的內(nèi)部實現(xiàn),從另一個委托的調(diào)用列表中移除委托的調(diào)用列表的最后一個匹配的項,通過RemoveImpl方法移除,RemoveImpl方法內(nèi)部實現(xiàn):

/// <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 的調(diào)用列表, value提供將從其中移除 source 的調(diào)用列表的調(diào)用列表.
一個新委托,其調(diào)用列表的構(gòu)成方法為:獲取 source 的調(diào)用列表,如果在 source 的調(diào)用列表中找到了 value 的調(diào)用列表,則從中移除 value 的最后一個調(diào)用列表.
 如果 value 為 null,或在 source 的調(diào)用列表中沒有找到 value 的調(diào)用列表,則返回 source.如果 value 的調(diào)用列表等于 source 的調(diào)用列表,或 source 為空引用,則返回空引用.
例如 : 在我們的實例 3中如果將 var deleRemove = (MyPersonDelegate)Delegate.Remove(personIntanceDelegate,dele);將source的值改為dele,將value的值改為personIntanceDelegate,則會返回一個Null.
泛型委托源碼解析
首先我們來看一下Action的委托:

.NET委托解析

在這里我們就拿出一些Action中有8個參數(shù)的來舉例了,注釋中標(biāo)明 : Encapsulates a method that has eight parameters and does not return a value. 意思 : 封裝另一個方法,具有八個參數(shù)并且不返回值的方法,在來看一下Action的定義,Action被定義為delegate類型void返回的方法,并且有1-18個指定為in的參數(shù),我們說過了in可以進(jìn)行逆變.

然后我們在來看Func

.NET委托解析

因為Func的參數(shù)也較多,我們這里只拿出帶有8個參數(shù)的來舉例了,從注釋中我們知道 : Encapsulates a method that has eight parameters and returns a value of the type specified by the ,封裝一個方法,具有八個參數(shù)并返回一個值所指定的方法,同時,返回值為out,可以進(jìn)行協(xié)變.

因為這篇文章篇幅已經(jīng)較長,對于異步委托在下篇文章中進(jìn)行解析.委托和反射留在反射的文章中進(jìn)行解析.另外希望本文能夠幫助到你了解到更多或者更深.

如果你覺得本文對你有幫助的話,請點右下角的推薦,或者直接關(guān)注我,后續(xù)將不斷更新.NET解析這一系列的文章....


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI