在这个大数据 / 云计算 / 人工智能研发普及的时代, Python 的崛起以及 JavaScript 的前后端的侵略, 程序员与企业似乎越来越青睐动态语言所带来的便捷性与高效性, 即使静态语言在性能, 错误检查等方面的优于静态语言. 对于. NETer 来说,.NET 做为一门静态语言, 我们不仅要打好. NET 的基本功, 如基本类型 / 语法 / 底层原理 / 错误检查等知识, 也要深入理解. NET 的一些高级特性, 来为你的工作减轻负担和提高代码质量.
ok, 咱们今天开始聊一聊. NET 中的 Emit.
一, 什么是 Emit?
Emit 含义为发出, 产生的含义, 这是. NET 中的一组类库, 命名空间为 System.Reflection.Emit, 几乎所有的. NET 版本 (Framework/Mono/NetCore) 都支持 Emit, 可以实现用 C# 代码生成代码的类库
二, Emit 的本质
我们知道. NET 可以由各种语言进行编写, 比如 VB,C++ 等, 当然绝大部分程序员进行. NET 开发都是使用 C# 语言进行的, 这些语言都会被各自的语言解释器解释为 IL 语言并执行, 而 Emit 类库的作用就是用这些语言来编写生成 IL 语言, 并交给 CLR(公共语言运行时)进行执行.
我们先来看看 IL 语言长什么样子:
(1) 首先我们创建一个 Hello,World 程序
- class Program
- {
- static void Main(string[] args)
- {
- Console.WriteLine("Hello World!");
- }
- }
(2) 将程序编译成 dll 文件, 我们可以看到在开发目录下生成了 bin 文件夹
(3) 向下寻找, 我们可以看到 dll 文件已经生成, 笔者使用 netcore3 进行开发, 故路径为 bin/Debug/netcoreapp3.0
(4) 这时候, 我们就要祭出我们的 il 查看神器了, ildasm 工具
如何找到这个工具? 打开开始菜单, 找到 Visual Studio 文件夹, 打开 Developer Command Prompt, 在打开的命令行中键入 ildasm 回车即可, 笔者使用 vs2019 进行演示, 其它 vs 版本操作方法均一致
(5) 在 dasm 菜单栏选择文件 ->打开, 选择刚刚生成的 dll 文件
(6) 即可查看生成 il 代码
有了 ildasm 的辅助, 我们就能够更好的了解 IL 语言以及如何编写 IL 语言, 此外, Visual Studio 中还有许多插件支持查看 il 代码, 比如 JetBrains 出品的 Resharper 插件等, 如果觉得笔者方式较为麻烦可以使用以上插件查看 il 代码
三, 理解 IL 代码
在上一章节中, 我们理解了 Emit 的本质其实就是用 C# 来编写 IL 代码, 既然要编写 IL 代码, 那么我们首先要理解 IL 代码是如何进行工作的, IL 代码是如何完成 C# 当中的顺序 / 选择 / 循环结构的, 是如何实现类的定义 / 字段的定义 / 属性的定义 / 方法的定义的.
IL 代码是一种近似于指令式的代码语言, 与汇编语言比较相近, 所以习惯于写高级语言的. NETer 来说比较难以理解
让我们来看看 Hello,World 程序的 IL 代码:
- IL_0000: nop
- IL_0001: ldstr "Hello World!"
- IL_0006: call void [System.Console]System.Console::WriteLine(string)
- IL_000b: nop
- IL_000c: ret
我们可以把 IL 代码看成栈的运行
第一条指令, nop 表示不做任何事情, 表示代码不做任何事情
第二条指令, ldstr 表示将字符串放入栈中, 字符串的值为 "Hello,World!"
第三条指令, call 表示调用方法, 参数为调用方法的方法信息, 并把返回的结构压入栈中, 使用的参数为之前已经入栈的 "Hello World!", 以此类推, 如果方法有 n 个参数, 那么他就会调取栈中 n 个数据, 并返回一个结果放回栈中
第四条指令, nop 表示不做任何事情
第五条指令, ret 表示将栈中顶部的数据返回, 如果方法定义为 void, 则无返回值
关于 Hello,world 程序 IL 的理解就说到这里, 更多的指令含义读者可以参考微软官方文档, 笔者之后也会继续对 Emit 进行讲解和 Emit 的应用
四, 用 Emit 类库编写 IL 代码
既然 IL 代码咱们理解的差不多了, 咱们就开始尝试用 C# 来写 IL 代码了, 有了 IL 代码的参考, 咱们也可以依葫芦画瓢的把代码写出来了
(1) 引入 Emit 命名空间
using System.Reflection.Emit;
(2) 首先我们定义一个 Main 方法, 入参无, 返回类型 void
- // 定义方法名, 返回类型, 输入类型
- var method = new DynamicMethod("Main", null, Type.EmptyTypes);
(3) 生成 IL 代码
- // 生成 IL 代码
- var ilGenerator = method.GetILGenerator();
- ilGenerator.Emit(OpCodes.Nop);
- ilGenerator.Emit(OpCodes.Ldstr,"Hello World!");
- ilGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] {
- typeof(string)
- })); // 寻找 Console 的 WriteLine 方法
- ilGenerator.Emit(OpCodes.Nop);
- ilGenerator.Emit(OpCodes.Ret);
(4) 创建委托并调用
- // 创建委托
- var helloWorldMethod = method.CreateDelegate(typeof(Action)) as Action;
- helloWorldMethod.Invoke();
(5)运行, 即输出 Hello World!
五, 小结
Emit 的本质是使用高级语言生成 IL 代码, 进而进行调用的的一组类库, 依赖 Emit 我们可以实现用代码生成代码的操作, 即编程语言的自举, 可以有效弥补静态语言的灵活性的缺失.
Emit 的性能非常好, 除了第一次构建 IL 代码所需要时间外, 之后只要将操作缓存在计算机内存中, 速度与手写代码相差无几
有许多著名. NET 类库均依赖于 Emit:
(.NET JSON 操作库)JSON.NET/Newtonsoft.JSON: GitHub 地址 https://github.com/JamesNK/Newtonsoft.Json
(轻量 ORM)Dapper:gituhb 地址 https://github.com/StackExchange/Dapper
(ObjectToObjectMapper)EmitMapper:GitHub 地址 https://github.com/MetSystem/EmitMapper
(AOP 库)Castle.DynamicProxy:GitHub 地址 https://github.com/castleproject/Core
学习 Emit:
.NET 官方文档: https://docs.microsoft.com/zh-cn/dotnet
.NET API 浏览器: https://docs.microsoft.com/zh-cn/dotnet/api/
来源: https://www.cnblogs.com/billming/p/emit-study.html