dotnet 委托的实现解析
缘起
最近被问到什么是.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 委托的实现解析的更多相关文章
- dotnet 委托的实现解析(2)开放委托和封闭委托 (Open Delegates vs. Closed Delegates)
前言 这是个人对委托的理解系列第二篇,部分翻译自 Open Delegates vs. Closed Delegates – SLaks.Blog,好像还没人翻译过,加上部分个人理解.希望能对大家理解 ...
- dotnet 手动解决 json 解析中不合法字符串
如果使用 Newtonsoft Json 解析字符串,字符串里面有不清真的格式,那么默认的解析将会炸掉.如果想要自己解决字符串中的不清真格式,可以使用传入 JsonSerializerSettings ...
- .NET委托解析
委托这个概念其实我们都很熟悉了,但是在使用的时候很多人还是无法去把控它,我们可以试想一下,在平时编码的时候,你是直接按照业务逻辑直接创建类,new出一个对象来进行操作的还是说有用到委托来更高效的完成一 ...
- DotNet加密方式解析--对称加密
离过年又近了一天,回家已是近在咫尺,有人欢喜有人愁,因为过几天就得经历每年一度的装逼大戏,亲戚朋友加同学的各方显摆,所以得靠一剂年终奖来装饰一个安稳的年,在这里我想起了一个题目“论装逼的技术性和重要性 ...
- DotNet加密方式解析--数字签名
马上就要过年回村里了,村里没有wifi,没有4G,没有流量,更加重要的是过几天电脑就得卖掉换车票了,得赶紧写几篇博客. 数据安全的相关技术在现在愈来愈变得重要,因为人们对于自身的信息都有一种保护的欲望 ...
- SugarSync网盘之XML解析
iOS的XML解析 刚在应用里支持了SugarSync网盘.其实也是第一次听说这个网盘,不过在国外貌似还蛮有名,这些都不是重点,重点是借此来总结一下iOS的XML解析.Xml想必也不陌生了,但是在iO ...
- xml数据解析
xml数据解析 在iPhone开发中,XML的解析有很多选择,iOS SDK提供了NSXMLParser和libxml2两个类库,另外还有很多第三方类库可选,例如TBXML.TouchXML.Kiss ...
- 解析.NET对象的跨应用程序域访问(下篇)
转眼就到了元宵节,匆匆忙忙的脚步是我们在为生活奋斗的写照,新的一年,我们应该努力让自己有不一样的生活和追求.生命不息,奋斗不止.在上篇博文中主要介绍了.NET的AppDomain的相关信息,在本篇博文 ...
- mybatis 3.x源码深度解析与最佳实践(最完整原创)
mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...
随机推荐
- PMD的使用学习
是什么? 静态代码分析器 能找出什么问题? 可能的 bugs - 空的 try/catch/finally/switch 声明 死码 - 未使用的本地变量,参数和私有方法 次优代码 - 无用的 Str ...
- [转载] IOS 获取网络图片的大小 改变 图片色值 灰度什么的方法集合
IOS 获取网络图片的大小 改变 图片色值 灰度什么的方法集合
- KVC替换系统的tabbar为自定义tabbar---秀清
CustomTabbar *tabbar = [[CustomTabbar alloc]init]; //KVC,更换系统的tabbar为自定义tabbar tabbar.tabbarDelegate ...
- VLAN大战三层交换机 以及无敌的Hybrid
VLAN与三层交换机 1.VLAN的概念与优势 2.VLAN的种类 3.静态VLAN的配置 4.Trunk介绍and配置 5.三层交换机原理 6.hybrid 1.当某个网络设备出现 故障后,就会不停 ...
- 【AGC035D】Add and Remove(脑洞 DP 分治)
题目链接 大意 给出\(N\)个数的序列,每次操作可以选择连续的三个数,将中间的那个数抽出,将另外两个数的数值加上中间那个数的数值. 一直执行以上操作直到只剩最后两个数,求最后两个数的所有可能的和的最 ...
- C#使用 WebRequest 异步获取网页并自动忽略SSL证书
C#使用 WebRequest 模拟浏览器请求访问网页并自动忽略HTTPS安全证书 以下两个C#异步方法,封装了WebRequest请求,支持忽略SSL证书. 作者:张赐荣 1.Get请求 ...
- NPM 错误、问题等汇总
一. npm的作用就是对Node.js依赖的包进行管理,也可以理解为用来安装/卸载Node.js需要装的东西 二. 1. 修改npm配置为淘宝的源下载: npm install -g cnpm --r ...
- CentOS7搭建yum源仓库(阿里源)
文章目录 注意:环境要求 1.配置服务器端yum 1.1.安装yum源工具 1.2.配置nginx 1.2.1.配置nginx页面目录 1.3.替换yum源文件 1.4.建立yum源仓库 2.配置客户 ...
- 实测Tengine开源的Dubbo功能
本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star. 搜索关注微信公众号"捉虫大师",后端技术分享,架构设计.性能优化.源码阅读 ...
- [自动化]ansible-系统安全加固整改
基线漏洞安全整改 修复环境:centos7及以上 安全基线的概念 安全基线是一个信息系统的最小安全保证,即该信息系统最基本需要满足的安全要求.信息 系统安全往往需要在安全付出成本与所能够承受的安全风险 ...