• 委托引入和本质


    前言

    虽然关于委托的文章园子中不胜枚举,但是要充分的理解委托的概念并且灵活运用,个人觉得还是要由浅入深,逐步推进,最后再来研究它的实质,这样才能达到事半功倍的效果,如果不信,请看下文,相信我所言非虚(当然也欢迎园友们拍砖和批评)!

    概念

    (1)用Delegate类表示委托,委托是一种数据结构,它引用静态方法或引用类实例及该类的实例方法。

    (2)解述:委托声明了一种类型,它用一种特定的参数以及返回类型来封装方法。对于静态方法,委托对象封装要调用的方法。对于实例方法,委托对象同时封装一个实例和该实例上的方法。如果有一个委托对象和一组适当的参数,则可以用这组参数来调用委托。

    委托引入

    平常我们就是写一个方法,然后再传入参数直接调用。如下:

     static void xsEat(string food)
     {
           Console.WriteLine("小三吃" + food);
     }
    
     xsEat("零食");  /*打印出小三吃零食*/

    现在我们用委托来实现代码如下:

        delegate void EatDelegate(string food);
        class Program
        {
            static void Main(string[] args)
            {
                EatDelegate xs = new EatDelegate(xsEat);
                xs("零食");
                Console.ReadKey();
            }
    
            static void xsEat(string food)
            {
                Console.WriteLine("小三吃" + food);
            }
        }

    我们用关键字 delegate 定义一个EatDelegate委托,在声明类的任何地方就能声明委托,委托声明的返回值和参数必须要和要调用的方法的签名一致。接下来对委托进行实例化并传入要代理的方法指针,此时实例化的对象 eat  就指向了方法 xs  ,再传入参数,结果打印出小三吃零食。如果接下来有小红(xh)和小明(xm)也过来吃零食,那我们也同样写这样的方法,代码如下:

     1     delegate void EatDelegate(string food);
     2     class Program
     3     {
     4         static void Main(string[] args)
     5         {
     6             EatDelegate xs = new EatDelegate(xsEat);
     7             xs("零食");
     8             EatDelegate xh = new EatDelegate(xhEat);
     9             xh("零食");
    10             EatDelegate xm = new EatDelegate(xmEat);
    11             xm("零食");
    12             Console.ReadKey();
    13         }
    14 
    15         static void xsEat(string food)
    16         {
    17             Console.WriteLine("小三吃" + food);
    18         }
    19 
    20         static void xhEat(string food)
    21         {
    22             Console.WriteLine("小红吃" + food);
    23         }
    24         static void xmEat(string food)
    25         {
    26             Console.WriteLine("小明吃" + food);
    27         }
    28     }

    上述分别打印出小三吃零食、小红吃零食、小明吃零食。看起来满足了需求,但是我们仔细想想明明是吃零食为什么还要实例化三次呢?代码能不能精简了,当然有办法,继续是委托,这个时候就要用到 委托链 了。所以我们对控制台的代码进行改写如下:

            static void Main(string[] args)
            {
                EatDelegate xs = new EatDelegate(xsEat);        
                EatDelegate xh = new EatDelegate(xhEat);
                EatDelegate xm = new EatDelegate(xmEat);
    
                EatDelegate eat;
                eat = xs + xh + xm;
                eat("零食");
    
                Console.ReadKey();
            }

    我们只需要把委托实例添加到委托链中即可同样达到了上述的效果。此时小三、小红和小明三个一起扎堆吃零食,后来陆陆续续的走了,通过这样一段描述我们用强大的委托链来实现,代码如下:

     1         static void Main(string[] args)
     2         {
     3             EatDelegate xs = new EatDelegate(xsEat);        
     4             EatDelegate xh = new EatDelegate(xhEat);
     5             EatDelegate xm = new EatDelegate(xmEat);
     6 
     7             EatDelegate eat;
     8             Console.WriteLine("小三、小红和小明一起吃零食");
     9             eat = xs + xh + xm;
    10             eat("零食");
    11             Console.WriteLine("小三有约出去了,就剩下小红和小明吃零食了");
    12             eat -= xs;
    13             eat("零食");
    14             Console.WriteLine("小红也走了,就剩下小明一个人吃零食了");
    15             eat -= xh;
    16             eat("零食");
    17             Console.ReadKey();
    18         }

    结果运行如图所示:

    我们由此知道-=或者+=号来更容易的对委托链中的元素进行操作,这样一来我们可以随意而且是任意妄为的对其元素进行操作。对于上面的方法我们继续进行精简,由于方法比较简单,微软大大为我们提供了一个便捷的方式来实现那就是 匿名方法 。我们将代码进行改写如下:

        delegate void EatDelegate(string food);
        class Program
        {
            static void Main(string[] args)
            {
    
                EatDelegate eat = null;
                eat += delegate(string food) { Console.WriteLine("小三吃" + food);};
                eat += delegate(string food) { Console.WriteLine("小红吃" + food); };
                eat += delegate(string food) { Console.WriteLine("小明吃" + food); };
                eat("零食");
                Console.ReadKey();
            }
        }

    通过匿名方法使得我们能更加方便的创建委托和使用委托。 

    通过上述我们不免心生疑问,它只能对静态方法进行调用,难道不能对动态方法进行调用呢?同时上面的代码未免有些冗余,也不能体现C#面向对象的思想!所以,鉴于此,我们对上述代码继续进行改写,如下:

     1     public class Person
     2     {
     3         public string Name { get; set; }
     4 
     5         public Person(string name)
     6         {
     7             this.Name = name;
     8         }
     9 
    10         public void Eat(string food)
    11         {
    12             Console.WriteLine(this.Name + "" + food);
    13         }
    14     }
    15     delegate void EatDelegate(string food);
    16     class Program
    17     {
    18         static void Main(string[] args)
    19         {
    20 
    21             Person xs = new Person("小三");
    22             Person xh = new Person("小红");
    23             Person xm = new Person("小明");
    24             EatDelegate xsEat = new EatDelegate(xs.Eat);
    25             EatDelegate xhEat = new EatDelegate(xh.Eat);
    26             EatDelegate xmEat = new EatDelegate(xm.Eat);
    27             EatDelegate eatChain = null;
    28             Console.WriteLine("小三、小红和小明一起吃零食");
    29             eatChain = xsEat + xhEat + xmEat;
    30             eatChain("零食");
    31             Console.WriteLine("小三有约,出去剩下小红和小明吃零食");
    32             eatChain -= xsEat;
    33             eatChain("零食");
    34             Console.WriteLine("小红也走了,只剩下小明一个人吃零食");
    35             eatChain -= xhEat;
    36             eatChain("零食");
    37             Console.ReadKey();
    38         }
    39     }

    结果和之前的效果一样:

    接下来我继续进行深入的改进,看到这里相信你也明白,方法是可以作为参数进行传递的,那么委托作为代理对象是不是可以作为方法的参数进行传递呢??我们试试,对上面代码继续进行改造,因为三个人吃零食是不确定的,所以会用到不确定参数数组,以及要吃的食物参数,所以改造如下:

     1     public class Person
     2     {
     3         public string Name { get; set; }
     4 
     5         public Person(string name)
     6         {
     7             this.Name = name;
     8         }
     9 
    10         public void Eat(string food)
    11         {
    12             Console.WriteLine(this.Name + "" + food);
    13         }
    14     }
    15     delegate void EatDelegate(string food);
    16     class Program
    17     {
    18         static void Main(string[] args)
    19         {
    20 
    21             Person xs = new Person("小三");
    22             Person xh = new Person("小红");
    23             Person xm = new Person("小明");
    24             EatDelegate xsEat = new EatDelegate(xs.Eat);
    25             EatDelegate xhEat = new EatDelegate(xh.Eat);
    26             EatDelegate xmEat = new EatDelegate(xm.Eat);
    27             EatDelegate eatChain = null;
    28             Console.WriteLine("小三、小红和小明一起吃零食");
    29             EatSomething("零食", xsEat, xhEat, xmEat);
    30             Console.WriteLine("小三有约,出去剩下小红和小明吃零食");
    31             EatSomething("零食", xhEat, xmEat);
    32             Console.WriteLine("小红也走了,只剩下小明一个人吃零食");
    33             EatSomething("零食", xmEat);
    34             EatSomething(null, null);
    35             Console.ReadKey();
    36         }
    37 
    38         static void EatSomething(string food, params EatDelegate[] ed)
    39         {
    40             EatDelegate eatChain = null;
    41             if (ed == null)
    42             {
    43                 Console.WriteLine("都走了,没人吃零食");
    44             }
    45             else
    46             {
    47                 foreach (var chain in ed)
    48                 {
    49                     eatChain += chain;
    50                 }
    51                 eatChain(food);
    52                 Console.WriteLine();
    53             }
    54         }
    55     }

    结果打印出:

    上述我们就实现了将委托作为参数进行传递并进行动态的调用 !

    至此,零食也吃完了,想必你对委托有了一定的了解了,那么难道你没有疑问?委托这么强大,它到底是什么东西?委托链又是怎样实现的呢?请看下文

    委托实质

    我们运用反编译工具查看上述生成的应用程序的IL代码,如图:

    由图中我们得出的信息是:(1)我们声明的委托 EatDelegate 原来是个类,而且还是可不可继承的密封类。(2)该委托还继承多播委托  MulticastDelegate ,并且该多播委托最终继承于 Delegate ,接下来我们看看这个委托:

    我们注意到在这委托里面有个IntPtr,后面接着的变量 _methodPtr 这个就是方法指针,我们说到委托链就是存的方法指针,那有很多方法它是怎么将这么多方法指针添加进去的呢?之前看过园友老赵中一篇文章,通过IL代码可以直接看c#代码,只是把你编写的C#代码进行了再一次编译而已于是我查看我写的 EatSomething 方法如图:

    方法中添加委托链这一段 eatChain += chain; 被编译成如图,于是我点击查看委托中的 Combine 方法,进去后又看到一个方法如图:

    若是在一个委托中添加另外一个委托,先是调用Delegate中的Combine方法,若检测第一个委托不为空则调用CombineImpl方法,将新添加的委托b添加进去,此时我查看CombineImpl方法,后面紧着就是出错抛异常,而且是个虚方法,确定应该是被重写了,既然声明的委托继承于多播委托,多播委托继承委托(Delegate),肯定是在这两个委托类中,终于在多播委托  MulticastDelegate 中重写了这个方法,如图:

    接着查看其方法得到如下

    通过这幅图和上幅图看到,多播委托中 _invovationList object对象在CombineImpl中被转换成了数组,同时将其num初始化为1,并将多播委托中的要添加的方法指针数量 _invocationCount 赋给num,一直往下,当其添加的数量不够时,此时将 _invovationList  重新创建数组,数组长度再赋值,有点类似StringBuilder!所以委托链的整个过程就是:新添加的委托方法将新创建一个委托对象,并将其方法指针存入最终的父类的变量Intptr中,同时将创建的对象添加到委托数组中去。

    Invoke

     声明委托后查看其IL代码都有三个方法,最主要的是Invoke方法,如图:

    咦,发现里面怎么有个参数food同时和声明委托方法签名一致。于是试试将声明委托的返回值改为有返回值的,此时编译生成再来查看果然是一样的。也就是说当你实例化委托对象所指的方法时,此时同样可以用实例化委托对象的 Invoke 指向该方法,也就是说调用委托其实就是调用委托中的Invoke方法,并遍历委托里面的数组,依次调用里面的方法。

    总结

    (1)声明委托的本质是声明一个类,并且该声明委托extends=>MulticastDelegate=>Delegate

    (2)委托链的本质就是新添加的方法将创建一个新的委托对象,并将其方法指针存入最终父类Delegate中的变量Intptr中,与此同时将新创建的对象添加到委托的对象数组中去

    (3)调用委托的本质是调用实例化委托对象中的Invoke方法,遍历其委托对象数组,依次调用数组中的方法

  • 相关阅读:
    JavaScript——BOM和DOM
    css-2
    Css-1
    storage size of 'xxx' isn't known问题出现的可能原因之一
    解决VS2010中winsock.h与winsock2.h冲突(重复定义)——转载
    SQLite : 解决“找不到请求的 .Net Framework 数据提供程序。可能没有安装”的问题
    使用 VirtualBox 虚拟机在电脑上运行 Android 4.0 系统,让电脑瞬间变安卓平板
    C#连接ACCESS的一个问题
    对硅谷和硅谷科技公司的十四问,全程干货
    nginx源码学习资源
  • 原文地址:https://www.cnblogs.com/CreateMyself/p/4716236.html
Copyright © 2020-2023  润新知