缘起

最近被问到什么是.Net中的委托。问题虽然简单却无从回答。只能说委托是托管世界的函数指针,这么说没啥大毛病,但也都是毛病(当时自己也知道这么说不太对,不过自己不太爱用这个也没准备确实没有更好的答案)。

执行效率

正巧前段时间看Core CLR的文档看到不同方式调用函数效率的比较正巧有这个,摘录如下。这段内容在 clr官方文档

为什么反射很慢 ?里。

Reading a Property (‘Get’)

Method Mean StdErr Scaled Bytes Allocated/Op
GetViaProperty 0.2159 ns 0.0047 ns 1.00 0.00
GetViaDelegate 1.8903 ns 0.0082 ns 8.82 0.00
GetViaILEmit 2.9236 ns 0.0067 ns 13.64 0.00
GetViaCompiledExpressionTrees 12.3623 ns 0.0200 ns 57.65 0.00
GetViaFastMember 35.9199 ns 0.0528 ns 167.52 0.00
GetViaReflectionWithCaching 125.3878 ns 0.2017 ns 584.78 0.00
GetViaReflection 197.9258 ns 0.2704 ns 923.08 0.01
GetViaDelegateDynamicInvoke 842.9131 ns 1.2649 ns 3,931.17 419.04

Writing a Property (‘Set’)

Method Mean StdErr Scaled Bytes Allocated/Op
SetViaProperty 1.4043 ns 0.0200 ns 6.55 0.00
SetViaDelegate 2.8215 ns 0.0078 ns 13.16 0.00
SetViaILEmit 2.8226 ns 0.0061 ns 13.16 0.00
SetViaCompiledExpressionTrees 10.7329 ns 0.0221 ns 50.06 0.00
SetViaFastMember 36.6210 ns 0.0393 ns 170.79 0.00
SetViaReflectionWithCaching 214.4321 ns 0.3122 ns 1,000.07 98.49
SetViaReflection 287.1039 ns 0.3288 ns 1,338.99 115.63
SetViaDelegateDynamicInvoke 922.4618 ns 2.9192 ns 4,302.17 390.99

上表分别列出了读取和设置属性通过不同方式的耗时等结果,我们可以看到直接通过属性读取和通过委托读取速度的平均值相差了接近10倍。这么看委托显然就不是函数指针了(函数指针的性能损失很小),那么下面就具体看下究竟是啥。

解析

先上实例代码如下:

    internal class HelloWorld
{
public static void HelloWorld1()
{
Console.WriteLine("hello world1");
} public delegate void SayHi(); public void Main()
{
SayHi? helloWorld = new SayHi(HelloWorld1);
helloWorld.Invoke();
}
}

很简单的代码,编译后用ILSpy打开。

元数据与IL

首先看下元数据表,毫不例外的在02 TypeDef表里找到了委托对象类型定义,毕竟一切皆对象,这个应该和事件是一个处理方法。

Name BaseType FieldList MethodList
SayHi 0x100000E 0x4000000 0x600006

剩下的表暂时先不看了(主要时间太长不记得类型方法在表里是咋对应起来的了)

下面先把类型SayHi的定义相关的IL代码贴出来

.class nested public auto ansi sealed SayHi
    extends [System.Runtime]System.MulticastDelegate
{
    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor (
            object 'object',
            native int 'method'
        ) runtime managed 
    {
    } // end of method SayHi::.ctor     .method public hidebysig newslot virtual 
        instance void Invoke () runtime managed 
    {
    } // end of method SayHi::Invoke     .method public hidebysig newslot virtual 
        instance class [System.Runtime]System.IAsyncResult BeginInvoke (
            class [System.Runtime]System.AsyncCallback callback,
            object 'object'
        ) runtime managed 
    {
    } // end of method SayHi::BeginInvoke     .method public hidebysig newslot virtual 
        instance void EndInvoke (
            class [System.Runtime]System.IAsyncResult result
        ) runtime managed 
    {
    } // end of method SayHi::EndInvoke } // end of class SayHi

这里第一个意外出来了,我一直以为委托是继承自System.Delegate但是没想到却是继承自System.MulticastDelegate。大家都知道后者继承前者主要就是是为了实现 += 这种多播委托的方式(也就是天天写事件用的这种方式)。 那么委托像事件那么注册好多个就是合情又合理了。也就是如下这种。

    internal class HelloWorld
{
public static void HelloWorld1()
{
Console.WriteLine("hello world1");
} public static void HelloWorld2()
{
Console.WriteLine("hello world2");
} public delegate void SayHi(); public void Main()
{
SayHi? helloWorld = new SayHi(HelloWorld1);
helloWorld += HelloWorld2;
helloWorld.Invoke();
}
}

果然是可以的,可惜大家(我们组的其他同事)宁愿用事件的方式,从来没见这么用过。

IL里定义的其他方法也没啥稀奇的Invoke这类的都是编译器加进去的,直接调用clr里处理,这里看不到实现。

小小的结论与一些疑惑

先说结论: (大胆猜测:)委托实际上和事件类似都是编译成一个对象,然后JIT执行到这个stub时再以FCall的形式(也许是QCall(FQ傻傻分不清),毕竟是动态生成的类不是很了解)调用到CLR里。我不爱用这个果然是对的。

再说说疑惑:

实际上最近在混合调试托管代码时遇到了很大问题。也就是

  • 只调试托管代码或者System.Private.CoreLib时没有问题。
  • 只调试core clr时也没问题(虽然大部分看不懂)。
  • 一旦混合调试时(托管代码调用clr的功能如 GetHashcode 或者 lock时)就有很多函数进不去,但是也不是也不是完全进不去,还是可以看见一部分混合调用的堆栈的。导致我现在很多只能靠猜,例如GetHashcode()是以FCall的形式调用到CLR里,直接在Core CLR里相关的代码打断点就能进入断点。

希望有缘人解答一下,我已经按clr的官方文档处理了,现在只剩下无奈与黔驴技穷了。

当然文中的其他问题也希望有缘人不吝指出。感谢。

dotnet 委托的实现解析的更多相关文章

  1. dotnet 委托的实现解析(2)开放委托和封闭委托 (Open Delegates vs. Closed Delegates)

    前言 这是个人对委托的理解系列第二篇,部分翻译自 Open Delegates vs. Closed Delegates – SLaks.Blog,好像还没人翻译过,加上部分个人理解.希望能对大家理解 ...

  2. dotnet 手动解决 json 解析中不合法字符串

    如果使用 Newtonsoft Json 解析字符串,字符串里面有不清真的格式,那么默认的解析将会炸掉.如果想要自己解决字符串中的不清真格式,可以使用传入 JsonSerializerSettings ...

  3. .NET委托解析

    委托这个概念其实我们都很熟悉了,但是在使用的时候很多人还是无法去把控它,我们可以试想一下,在平时编码的时候,你是直接按照业务逻辑直接创建类,new出一个对象来进行操作的还是说有用到委托来更高效的完成一 ...

  4. DotNet加密方式解析--对称加密

    离过年又近了一天,回家已是近在咫尺,有人欢喜有人愁,因为过几天就得经历每年一度的装逼大戏,亲戚朋友加同学的各方显摆,所以得靠一剂年终奖来装饰一个安稳的年,在这里我想起了一个题目“论装逼的技术性和重要性 ...

  5. DotNet加密方式解析--数字签名

    马上就要过年回村里了,村里没有wifi,没有4G,没有流量,更加重要的是过几天电脑就得卖掉换车票了,得赶紧写几篇博客. 数据安全的相关技术在现在愈来愈变得重要,因为人们对于自身的信息都有一种保护的欲望 ...

  6. SugarSync网盘之XML解析

    iOS的XML解析 刚在应用里支持了SugarSync网盘.其实也是第一次听说这个网盘,不过在国外貌似还蛮有名,这些都不是重点,重点是借此来总结一下iOS的XML解析.Xml想必也不陌生了,但是在iO ...

  7. xml数据解析

    xml数据解析 在iPhone开发中,XML的解析有很多选择,iOS SDK提供了NSXMLParser和libxml2两个类库,另外还有很多第三方类库可选,例如TBXML.TouchXML.Kiss ...

  8. 解析.NET对象的跨应用程序域访问(下篇)

    转眼就到了元宵节,匆匆忙忙的脚步是我们在为生活奋斗的写照,新的一年,我们应该努力让自己有不一样的生活和追求.生命不息,奋斗不止.在上篇博文中主要介绍了.NET的AppDomain的相关信息,在本篇博文 ...

  9. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

随机推荐

  1. oracle修改密码、添加用户及授权

    解锁某个用户 sqlplus/as sysdba; alter user scott account unlock; 忘记密码处理 登录:sqlplus/as sysdba;修改:alter user ...

  2. iOS UIWebView与JavaScript的交互 相关资料

    UIWebView自适应宽度 iOS UIWebView中javascript与Objective-C交互.获取摄像头 iOS中JavaScript和OC交互 iOS与js交互,获取webview完整 ...

  3. go基础——数值传递与值传递

    package main import "fmt" /* 1.值类型:存储的是数值本身,数据传递给其他变量时传递的是数据的副本. int,float,string,bool,arr ...

  4. 泛型、Junit

    一. 泛型 1.泛型的好处 **1)编译时,检查添加元素的类型,提高了安全2)减少了类西较换的次数,提高效率 不使用泛型Dog -> Object -> Dog //放入到ArrayLis ...

  5. 【Gym100837F】Controlled Tournament(状压Dp 搜索剪枝)

    题目链接 大意 现有\(N\)个人要打比赛,知道任意两个人间打比赛的胜负关系. 要求在 深度最小 的情况下,根为\(M\)的 竞赛树 的个数. 满足\(1\le M\le N\le 16\) 思路 虑 ...

  6. 个人觉得好用的Idea插件

    Intellij IDEA插件 排名不分先后 1. Codota 代码智能提示插件 只要打出首字母就能联想出一整条语句,这也太智能了,还显示了每条语句使用频率.原因是它学习了我的项目代码,总结出了我的 ...

  7. Linux常用命令在Ubuntu 16下(个人笔记)

    可以通过 tab键来补全提示命令或者目录,终端命令的格式: 命令 [-选项,多个选项可以结合写] [参数] , 大多数情况可以通过 ctrl c 退出命令 磁盘管理 pwd 查看当前所在目录 即:pr ...

  8. 5.Flink实时项目之业务数据准备

    1. 流程介绍 在上一篇文章中,我们已经把客户端的页面日志,启动日志,曝光日志分别发送到kafka对应的主题中.在本文中,我们将把业务数据也发送到对应的kafka主题中. 通过maxwell采集业务数 ...

  9. win10 VScode配置GCC(MinGW)

    前提 安装 Visual Studio Code 安装 C/C++ 扩展 for VS Code 也可以在vscode的extension界面搜索'c'查找插件安装 获取最新的 Mingw-w64 , ...

  10. Vue 源码解读(3)—— 响应式原理

    前言 上一篇文章 Vue 源码解读(2)-- Vue 初始化过程 详细讲解了 Vue 的初始化过程,明白了 new Vue(options) 都做了什么,其中关于 数据响应式 的实现用一句话简单的带过 ...