• 浅谈.NET中的委托


      委托、事件、反射、特性等.NET中的高级特性,对这些特性的掌握和熟练运用,往往成为.NET程序员从入门到中级的评价标准。这篇文章DebugLZQ谈一下.NET中的委托特性在.NET框架中是如何实现的,如文章题目说说:浅谈.NET中委托的本质。

      委托这一特性对于有过C++编程经验的程序员来说并不陌生,C++中的函数指针和委托确实非常相似,很多人喜欢吧.NET中的委托称为“安全的函数指针”。DebugLZQ这里不去争论这种说法正不正确,但委托确实实现了和函数指针非常相似的功能,那就是程序回调指定方法的机制。

      1、委托的基本原理

      在委托的内部,包含了一个指向某个方法的指针,在这一点上,委托的实现机制和C++的函数指针完全相同。之所以称委托为安全的,是因为委托和其他.NET成员一样,是一种类型,任何委托对象都是System.Delegate的某个派生类的一个对象,在.NET框架中,委托的类结构如下图所示:

      从这个结构中可以看出任何自定义的委托都继承自父类System.Delegate。在这个类中,定义了大部分委托的特性,而关于System.MulticastDelegate的特性,后面将介绍。

    下面分析一个具体的例子,代码如下:

    using System;
    
    namespace 委托本质
    {
        class Program
        {
            /// <summary>
            /// DebugLZQ解析委托
            /// 定义委托。
            /// </summary>
            /// <param name="i">接受一个整型参数</i>
            public delegate void TestDelegate(int i);
            static void Main(string[] args)
            {
                //调用委托方法
                TestDelegate d = new TestDelegate(PrintMessage1);
                d(0);
                d(1);            
                Console.Read();
            }
    
            /// <summary>
            /// 一个静态方法,符合TestDelegate的定义
            /// </summary>
            /// <param name="i">整型参数</param>
            static void PrintMessage1(int i)
            {
                Console.WriteLine("" + i + "个方法");
            }
        }
    }

      在上面代码中,首先通过public delegate void TestDelegate(int i);定义一个名为TestDeletate的新类型,这个类型继承自System.MuticastDelegate。而且它会包含一个名为Invoke的方法,该方法接受一个整型的参数并且没有返回值。这些步骤是由C#编译器自动完成的。
      然后声明一个TestDelegate的对象d,并且绑定了一个静态的方法 void PrintMessage1到该委托上。需要注意的是委托可以接受实例方法,也可以接受静态方法,其区别将在下面讲述。最后,也是令人期待的部分,d被调用执行:d(0)、d(1);这里各位可能会产生困惑,事实上,这只是C#设计者为简化程序员的输入而设计的一种语法而已。在本质上,委托的调用就是执行了在定义委托时生成的Invoke方法。

      为了容易理解,我们完全可以把委托的调用部分写成如下形式:

    d.Invoke(0);
    d.Invoke(1);

      当委托执行时,.NET 检查委托对象并找到PrintMessage1(int i)方法,然后把参数传递给该方法并且执行。

    下面是程序的运行结果:

    小结:委托是一类继承自System.Delegate的类型,每个委托对象至少包含一个指向某个方法的指针,该方法可以是实例方法,也可以是静态方法。委托实现了回调方法的机制,能够帮助程序员更加简洁优美的设计面向对象程序。

      2.委托的内部结构

      为了进一步弄清楚委托的本质,我们来介绍下委托的内部结构。下面我们先看一下System.Delegate的结构,如下图所示:

      _target是一个指向目标实例的引用。当绑定一个实例方法给委托时,该参数会被设置为该方法所在类型的一个实例对象。而当绑定一个静态方法给委托时,该参数会被设置为null。(委托回调实例方法和静态方法的本质区别就在这!)

      _methodPtr是一个指向绑定方法代码的指针,和C++中的函数指针及其类似。绑定静态方法或是实例方法在这个成员的设置上并没有不相同。

      事实上,对于继承自System.MuticastDelegate的自定义委托来说,还有另外一个成员变量:_prev,该指针指向委托链中的下一个委托,这个将在下面进行介绍。

      3.委托链[链式委托]

      委托链是一个由委托组成的链表,而不是一个新的东西。从1中的图可以看到,所有的自定义委托都直接集成自System.MulticastDelegate类型,这个类型即是为委托链而设计的。

      为了更彻底的理解链式委托的实现机制,有必要来看一下System.MulticastDelegate的内部成员,其重要的三个成员如下图所示:

      前面已经讲过System.Delegate的两个内部成员,System.MulticastDelegate继承了这两个成员,并且添加了一个_prev成员,该成员是一个委托的引用变量,当摸个委托被串联到当前委托的后面时,该成员会被设置指向那个后续委托实例对象。.NET就靠这一引用来逐一找到当前委托的所有后续委托并依此执行。

      DebugLZQ再次强调,链式委托是指一个委托的链表,而不是指另外一类特殊的委托,当执行链上的一个方法时,后续委托将会被依此执行。如何改变执行顺序,后面将介绍。System.MuticastDelegate定义了对链式委托的支持。在System.Delegate的基础上,它增加了一个指向后续委托的指针,这样就实现了一个简单的链表结构。

      为了更深一层的理解链式委托,下面来看一个链式委托的例子: 

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace 链式委托
    {
        class Program
        {
            /// <summary>
            /// 定义的委托。
            /// </summary>
            public delegate void TestMultiDelegate();
            static void Main(string[] args)
            {
                //申明一个委托变量,并绑定第一个方法
                TestMultiDelegate handler = new TestMultiDelegate(PrintMessage1);
                //绑定第二个方法
                handler += new TestMultiDelegate(PrintMessage2);
                //绑定第三个方法
                handler += new TestMultiDelegate(PrintMessage3);
                //检查结果
                handler();
                Console.Read();
            }
            static void PrintMessage1()
            {
                Console.WriteLine("第一个方法");
            }
            static void PrintMessage2()
            {
                Console.WriteLine("第二个方法");
            }
            static void PrintMessage3()
            {
                Console.WriteLine("第三个方法");
            }
        }
    }

     关于以上代码,不做过多的解释。为了便于理解,把上面的代码的核心部分用一种比较复杂的方式进行重写:

    TestMultiDelegate handler = new TestMultiDelegate(PrintMessage1);           
    TestMultiDelegate handler2 = new TestMultiDelegate(PrintMessage2);
    TestMultiDelegate handler3 = new TestMultiDelegate(PrintMessage3);
    TestMultiDelegate handlerhead = handler + handler2 + handler3;
    handlerhead.Invoke();

    事实上,这两种写法的本质完全一样,只是第一种写法更简洁通用。
    程序的输出如下:

    从结果可以看出,当程序调用委托链的链头handlerhead时,挂在这个委托之后的所有委托方法都被依此调用了。

    我们不妨试下把handlerhead的调用改为如下代码:

    handler();

    执行之后,会发现执行结果和替换之前一模一样。事实上,上面这段代码没有为handlerhead分配任何委托实例,而仅仅把所有县城的委托串联,并让handlerhead引用到委托链的头上,所以handlerhead和handler实际上引用了同一个委托实例。

      4.必要说明

       链式委托的执行顺序是:按照委托链上的顺醋从当前委托开始依次往后执行,如果有需要可以使用GetInvocationList()方法来获得委托链上所有需要执行的委托,并且按照任何希望的顺序去执行(Invoke)他们。

      委托可以是带有返回值的方法,但多余一个带返回值的方法被添加到委托链中时,程序员需要手动地调用委托链上的每个方法,否则委托使用者智能得到委托链上最后一个被执行的方法的返回值。

      委托的应用场合通常是任务的执行者把细节工作进行再分配,执行者确切地知道什么工作将要被执行,但却把执行细节委托给其他组件、方法或者程序集。

      5.结束语

      本文旨在把.NET中的委托给说清楚,可能存在纰漏不妥的地方欢迎批评指正!
      今天是七夕情人节,在这里向现在还奋战在一线的程序员们致敬,祝你们早日找到心仪的另一半!

      最后请点击下面的绿色通道--关注DebugLZQ,共同交流进步~ 

  • 相关阅读:
    [Android] ImageView.ScaleType设置图解 【转载】
    ASP.NET Web API 配置返回的json字段的格式以及Action返回HttpResponseMessage类型和IHttpActionResult类型
    ASP.NET Web API 上传文件
    ASP.NET Web API 全局权限和全局异常处理
    ASP.NET Web API 中的异常处理(转载)
    ASP.NET Web API 2 中的属性路由使用(转载)
    使用ASP.NET Web API自带的类库实现对CORS的支持(在开发中使用这种方式)(转载)
    通过扩展让ASP.NET Web API支持W3C的CORS规范(转载)
    IoC容器Autofac
    C#开发微信公众平台-就这么简单(附Demo)(转载)
  • 原文地址:https://www.cnblogs.com/DebugLZQ/p/2649813.html
Copyright © 2020-2023  润新知