在这个大数据/云计算/人工智能研发普及的时代,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地址

  (轻量orm)dapper:gituhb地址

  (objecttoobjectmapper)emitmapper:github地址

  (aop库)castle.dynamicproxy:github地址

  学习emit:

  .net官方文档:

  .net api浏览器:

  之后作者将继续讲解.net emit的相关内容和应用,感谢阅读