分享

.NET委托解析

 寒江雪书院 2015-06-30

委托这个概念其实我们都很熟悉了,但是在使用的时候很多人还是无法去把控它,我们可以试想一下,在平时编码的时候,你是直接按照业务逻辑直接创建类,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的值


【四】委托的源码解析(反编译查看委托回调静态与实例的区别,以及委托链的本质)


 接下来我们对前面所提到的委托回调和委托链进行反编译,查看委托在调用的时候内部是如何实行的,先贴出委托的部分源码 : 




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,可以进行协变.


因为这篇文章篇幅已经较长,对于异步委托在下篇文章中进行解析.委托和反射留在反射的文章中进行解析.另外希望本文能够帮助到你了解到更多或者更深.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多