浅谈.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,共同交流进步~ 

原文地址:https://www.cnblogs.com/DebugLZQ/p/2649813.html