委托引入和实质

前言

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

概念

(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)委托的本质是一个类

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

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

时间: 2024-10-29 08:20:35

委托引入和实质的相关文章

C#秘密武器之委托

概述 在C#的世界里,委托是无处不在,尤其在.NET自己封装的框架里到处都有其身影,所以掌握委托就很有必要了!那什么是委托呢?其实委托就是一种数据类型,跟int等东东差不多,只不过在使用时要自己先去构建一个委托数据类型(不像int微软已为你准备好),然后声明一个委托类型的变量,并为其赋值,赋值的对象只能是方法,最后通过委托变量就可以进行方法调用了! 委托的简单使用 如下定义了一个委托类型 - Calculator: delegate int Calculator (int x); 此委托适用于任

匹夫细说C#:庖丁解牛聊委托,那些编译器藏的和U3D给的

0x00 前言 由于工作繁忙所以距离上一篇博客已经过去一个多月的时间了,因此决心这个周末无论如何也得写点东西出来,既是总结也是分享.那么本文主要的内容集中在了委托的使用以及内部结构(当然还有事件了,但是受制于篇幅故分为两篇文章)以及结合一部分Unity3D的设计思考.当然由于时间仓促,文中难免有一些疏漏和不准确,也欢迎各位指出,共同进步. 0x01 从观察者模式说起 在设计模式中,有一种我们常常会用到的设计模式——观察者模式.那么这种设计模式和我们的主题“如何在Unity3D中使用委托”有什么关

快速理解C#高级概念(一) Delegate委托

做.NET开发很久,最近重新温习<C#高级编程>一书.发现很多曾经似懂非懂的问题,其实也是能够慢慢钻研慢慢理解的. 所以,打算开写<C#高级编程系列>博文.其中会借鉴<C#高级编程>一书的概念,也会参照其他高手的博文,希望大家谅解.有不对的地方,欢迎指正. (另:本博文不会讲解定义,语法方面的基础知识.) 下面如题,我们来讲委托. Delegate委托,在.NET中应用的非常广泛.会涉及到Lambda表达式,事件,匿名方法等(请关注后续博文). 那么何为委托? 通俗的来

深入理解C# 委托(delegate)-戈多编程

今天来谈谈委托,深入理解委托,本文来自各大神经验总结. 1.委托是什么? 委托类型的声明与方法签名相似. 它有一个返回值和任意数目任意类型的参数,是一种可用于封装命名方法或匿名方法的引用类型. 委托类似于 C++ 中的函数指针:但是,委托是类型安全和可靠的. (1)从数据结构来讲,委托和类一样是一种用户自定义类型 (2)从设计模式来讲,委托(类)提供了方法(对象)的抽象 既然委托是一种类型,那么它存储的是什么数据? 我们知道,委托是方法的抽象,它存储的就是一系列具有相同签名和返回回类型的方法的地

UNITY_委托和事件

UNITY_委托和事件 参考资料: Unity3D脚本编程-使用C#语言开发跨平台游戏-陈嘉栋 观察者模式 主题(Subject)管理某些数据,当主题的数据发生改变时,会通知已经注册(Register)的观察者(Observer),而这些已经注册的观察者会受到数据改变的通知并作出相应反应. 观察者模式定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新. Unity提供的机制:SendMessage()和BroadcastMessage() 缺点: 过于依赖反射

AOP 工厂对象之ScopedProxyFactoryBean 原理解析

J2EE开发者,对购物车这个概念太熟悉了.存在于Session周期中.今天就说说,如果用Spring管理购物车,怎么处理. 使用场景 <bean id="cart" class="com.hellojd.jpetstore.domain.model.Cart"   scope="session">     <aop:scoped-proxy  proxy-target-class="true"/> &l

springboot相关面试题

springboot和springmvc的区别 spring boot 内嵌tomcat,Jetty和Undertow容器,可以直接运行起来,不在再做部署: spring boot 自动配置,减少了xml文件的大量配置:降低了项目搭建的复杂度 Spring MVC是基于 Servlet 的一个 MVC 框架 主要解决 WEB 开发的问题,因为 Spring 的配置非常复杂,各种XML. JavaConfig.hin处理起来比较繁琐.于是为了简化开发者的使用,从而创造性地推出了Spring boo

编写高质量代码改善C#程序的157个建议——建议39:了解委托的实质

建议39:了解委托的实质 理解C#中的委托需要把握两个要点: 1)委托是方法指针. 2)委托是一个类,当对其进行实例化的时候,要将引用方法作为它的构造方法的参数. 设想这样一个场景:在点对点文件传输过程当中,我们要设计一个文件传输类,该传输类起码要满足下面几项功能: 传输问题件: 按照百分制通知传输进度: 传输类能够同时被控制台程序和WinForm应用程序使用. 由于要让通知本身能够被控制台程序和WinFrom应用程序使用,因此设计这个文件传输类在进行进度通知时,就不能显示调用: Console

[C# 基础知识系列]专题一:深入解析委托——C#中为什么要引入委托

转自http://www.cnblogs.com/zhili/archive/2012/10/22/Delegate.html 引言: 对于一些刚接触C# 不久的朋友可能会对C#中一些基本特性理解的不是很深,然而这些知识也是面试时面试官经常会问到的问题,所以我觉得有必要和一些接触C#不久的朋友分享下关于C#基础知识的文章,所以有了这个系列,希望通过这个系列让朋友对C#的基础知识理解能够更进一步.然而委托又是C#基础知识中比较重要的一点,基本上后面的特性都和委托有点关系,所以这里就和大家先说说委托