• .NET高级特性-Emit(1)


      在这个大数据/云计算/人工智能研发普及的时代,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官方文档:https://docs.microsoft.com/zh-cn/dotnet

      .NET API浏览器:https://docs.microsoft.com/zh-cn/dotnet/api

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

  • 相关阅读:
    alg--动态规划(dynamic planning)
    alg--分治法
    汇编-理解call,ret
    汇编--实验7
    leetCode笔记--binary tree
    leetCode笔记--(1)
    C#获取当前路径的方法如下
    VS2013 快捷键 与 RESHARPER 冲突
    使用Visual Studio 2013进行单元测试--初级篇
    VS 插件
  • 原文地址:https://www.cnblogs.com/billming/p/emit-study.html
Copyright © 2020-2023  润新知