【C#进阶系列】16 委托

委托主要是为了实 现回调函数机制,可以理解为函数指针(唯一不同的在于多了委托链这个概念)。

然而用的时候可以这么理解,但是委托的内部机制是比较复杂的。

一个委托的故事

delegate void razor(String userName);

一个简单的委托被定义了。

实际上在编译后这段代码就和下面的代码很像了:

  class Razor : System.MulticastDelegate {
        //构造函数
        public Razor(Object @object,IntPtr method);
        public virtual void Invoke(Int32 value);
        //实现对回调方法的异步回调
        public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object @object);
        public virtual void EndInvoke(IAsyncResult result);
    }

也就是说委托实际上是一个派生自MulticastDelegate的类,其中有四个方法。

而由于继承MulticastDelegate类,所以还继承了MulticastDelegate类的字段、方法和属性。

有三个字段最重要,分别是指向委托传进的实例方法的操作对象_target(如果是静态方法就是NULL),CLR用来标识回调方法的_methodPtr(这里不是指针,而是一个内部的参数值,用以表示某方法),构造委托链时委托数组的引用_invokationList(在委托通过Delegate.Combine或者+=绑定了多个回调函数后才有值,否则为null).

而在使用委托的时候会:

先去看此委托的_invokationList是否为null,为null就说明没有建立委托链,那么实际上就用了委托Razor(实际上是一个类的实例),调用Invoke方法,此时会传给Invoke方法一个参数值,它就是_methodPtr存放的内部的参数值,用于标识传进来的实例方法的参数值。

如果_invokationList不为null说明已经建立了委托链,那么久分别去调用委托数组里面各个委托的Invoke方法,执行的方法自然是各个委托里面_methodPtr指定的方法。

而至于移除委托(用Delegate.Remove或者-=)。

执行委托链中的函数是按照绑定的顺序开始执行,但是如果有返回,那么返回的一定是最后一个绑定的函数的返回结果。

然而这种执行委托链的方式有一些问题,比如某个委托链中的委托抛了异常或者因为调用数据库查询的阻塞时间太长,导致后面的委托函数都无法执行。

所以MulticastDelegate类提供了一个实例方法GetInvokationList(),他会返回_invokationList指向的Delegate数组。

泛型委托

泛型委托是为了解决相似的委托过多的问题,而且.NET提供的泛型委托也很简单,17个参数不同的Action委托和17个参数不同的Func委托.

其中Action委托都是返回值为void,而Func委托的返回值类型为自己指定的类型的TResult。

然而如果需要使用ref和out关键字以传引用的方式传递参数,那么就不得不定义自己的委托。因为泛型委托的协变和逆变的时候会用到in和out标识,为了搞混就不能这么用。(此处可参考泛型那一章)

C#关于委托的语法糖

一个正常的委托是下面这样的

    static void Main(string[] args)
        {
            Razor razor=null;
            razor += new Razor(Blower);
            razor("Troy123");
            Console.Read();
        }
        static void Blower(String userName) {
            Console.WriteLine(userName + ":实际上这是一个吹风机");
        }

然而

razor += new Razor(Blower);

这样的语法看起来很奇怪(一开始奇怪,其实还好啦),所以C#提供个一些简化的语法:

  • 不需要构造委托对象

    • 一些函数会像下面那样可以直接传入回调方法,而不是像之前一样需要new Razor这样去调用构造委托对象。表面上是如此,实际上只是在内部做了处理,还是会去构造委托对象然后调用。

              void 回调函数(object obj) {
                  Console.WriteLine(obj);
              }
              ThreadPool.QueueUserWorkItem(回调方法, 5);
  • 不需要定义回调方法(lambda表达式)
    • 之前的代码可以写成这样

      ThreadPool.QueueUserWorkItem(l=>Console.WriteLine(o),5);
    • lambda表达式这种方法看起来很怪异,然而用习惯了非常好用。这是通过创建匿名函数去取代回调函数。(这种玩法虽然好,但是如果一个回调函数被多个地方调用,那么还是用之前的方法好一点)
    • 匿名函数一般被标记为static和private。前者是因为不需要创建额外的this参数所以效率更高,后者是为了安全性。(除非匿名函数用了实例成员)
      • 以下是一些具体例子:

        delegate void Bar(out Int32 z);
        
          Func<string> f = () => "Troy";
           Func<Int32,string> f1 = (Int32 l) => l.ToString();
           Func<Int32,string> f2 = (l) => l.ToString();//推断类型
           Func<Int32, Int32, string> f3 = (l, m) => { Console.WriteLine(m); return l.ToString(); };//多条语句加大括号
           Bar b = (out Int32 n) => n = 5;//对于引用类型,必须显示指定
  • 局部变量不需要手动包装到类中即可传给回调方法
    • 看起来很绕,实际上很简单。就是lambda表达式可以直接使用局部变量和参数,而不需要你去传参。

委托和反射

System.Delegate.MethodInfo提供了一个CreateDelegate方法,顾名思义,就是去动态创建一个委托。

然后可以用Delegate.DynamicInvoke方法去动态调用它。

关于这个具体的也没有想到太多的地方会用到,毕竟见的世面少了。

时间: 2024-08-06 21:21:09

【C#进阶系列】16 委托的相关文章

.Net进阶系列(8)-委托和事件(二)

一. 泛型委托 所谓的泛型委托,即自定义委托的参数可以用泛型约束,同时内置委托Func和Action本身就是泛型委托. 将上一个章节中的Calculator类中的方法用自定义泛型委托重新实现一下. 1 public class Calculator2 2 { 3 //传统解决方案一:在该类中声明多个方法,分别是加倍.平方.立方的方法 4 5 6 //传统解决方案二:在该类中声明一个万能方法,通过传递不同的参数类型来区分是执行加倍还是平方或者立方操作 7 8 9 //解决方案三:声明一个万能方法,

JavaScript进阶系列06,事件委托

在"JavaScript进阶系列05,事件的执行时机, 使用addEventListener为元素同时注册多个事件,事件参数"中已经有了一个跨浏览器的事件处理机制.现在需要使用这个事件处理机制为页面元素注册事件方法. □ 点击页面任何部分触发事件 创建一个script1.js文件. (function() { eventUtility.addEvent(document, "click", function(evt) { alert('hello'); }); }(

JavaScript进阶系列07,鼠标事件

鼠标事件有Keydown, Keyup, Keypress,但Keypress与Keydown和Keyup不同,如果按ctrl, shift, caps lock......等修饰键,不会触发Keypress事件,而会触发Keydown和Keyup事件,这就是Keypress事件与Keydown.Keyup事件的不同之处.另外,通常使用Keypress事件来获取用户输入信息. 继续使用"JavaScript进阶系列05,事件的执行时机, 使用addEventListener为元素同时注册多个事件

Wireshark入门与进阶系列(一)

摘自http://blog.csdn.net/howeverpf/article/details/40687049 Wireshark入门与进阶系列(一) “君子生非异也,善假于物也”---荀子 本文由CSDN-蚍蜉撼青松 [主页:http://blog.csdn.net/howeverpf]原创,转载请注明出处! 你在百度上输入关键字“Wireshark.使用.教程”,可以找到一大堆相关的资料.那么问题来了, 为什么我还要写这个系列的文章? 前面你能搜到的那些资料,大部分可能存在两个小问题:

Wireshark入门与进阶系列(二)

摘自http://blog.csdn.net/howeverpf/article/details/40743705 Wireshark入门与进阶系列(二) “君子生非异也,善假于物也”---荀子 本文由CSDN-蚍蜉撼青松 [主页:http://blog.csdn.net/howeverpf]原创,转载请注明出处! 上一篇文章我们讲了使用Wireshark进行数据包捕获与保存的最基本流程,更通常的情况下,我们对于要捕获的数据包及其展示.存储可能有一定要求,例如: 我们希望捕获的数据包中对我们有用

Jenkins进阶系列之——18Jenkins语言本地化

在Jenkins中,英语一大片,看着各种蛋疼.非常高兴的是,Jenkins作为一个主流流行的持续构建工具,提供了一个本地化语言的配置界面. 你可以找到它,在Jenkins每页的左下角.如下图: 点击帮助我们本地化当前页,出现如下窗口(友情提示:请关闭自动刷新功能) Locale:你翻译的语言的种类,Chinese (Simplified)简体中文. Your Name:[可选]你的称呼(真实姓名.网名.代号均可) I contribute my translations to the Jenki

C#进阶系列——MEF实现设计上的“松耦合”(一)

前言:最近去了趟外地出差,介绍推广小组开发的框架类产品.推广对象是本部门在项目上面的同事——1到2年工作经验的初级程序员.在给他们介绍框架时发现很多框架设计层面的知识他们都没有接触过,甚至没听说过,这下囧了~~于是乎在想该如何跟他们解释MEF.AOP.仓储模式等方面的东东.本来 C#基础系列 应该还有两篇关于异步的没有写完,奈何现在要推广这些个东西,博主打算先介绍下项目中目前用到的些技术,异步的往后有时间再做分享.C#进阶系列主要围绕MEF.AOP.仓储模式.Automapper.WCF等展开.

asp.net core 系列 16 Web主机 IWebHostBuilder

原文:asp.net core 系列 16 Web主机 IWebHostBuilder 一.概述 在asp.net core中,Host主机负责应用程序启动和生存期管理.host主机包括Web 主机(IWebHostBuilder)和通用主机(IHostBuilder).Web 主机是适用于托管 Web 应用:通用主机(ASP.NET Core 2.1 或更高版本)是适用于托管非 Web 应用:在未来的版本中,通用主机将适用于托管任何类型的应用,包括 Web 应用. 通用主机最终将取代 Web

Spring Boot进阶系列三

Thymeleaf是官方推荐的显示引擎,这篇文章主要介绍怎么让spring boot整合Thymeleaf.  它是一个适用于Web和独立环境的现代服务器端Java模板引擎. Thymeleaf的主要目标是给开发工作流程带来优雅的自然模板 - 可以在浏览器中正确显示的HTML,也可以用作静态原型,从而在开发团队中实现更强大的协作.通过Spring Framework模块,与喜欢的工具的集成,Thymeleaf是HTML5 JVM Web开发的理想选择. 1.自然模板官方示例 用Thymeleaf

C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(二)

前言:上篇介绍了下封装BootstrapHelper的一些基础知识,这篇继续来完善下.参考HtmlHelper的方式,这篇博主先来封装下一些常用的表单组件.关于BootstrapHelper封装的意义何在,上篇评论里面已经讨论得太多,这里也不想过多纠结.总之一句话:凡事有得必有失,就看你怎么去取舍.有兴趣的可以看看,没兴趣的权当博主讲了个“笑话”吧. 本文原创地址:http://www.cnblogs.com/landeanfen/p/5746166.html BootstrapHelper系列