• .NET Emit 入门教程:第五部分:动态生成方法(MethodBuilder 与 DynamicMethod)


    前言:

    当我们涉及到在运行时生成和定义方法时,便需要使用到C#中的两个关键类之一:MethodBuilder 或 DynamicMethod。

    这两者都属于反射(Reflection.Emit)的一部分,允许我们以动态的方式创建方法。

    两者各有侧重,使用方式大体相同,本篇文章我们先介绍 MethodBuilder,再介绍 DynamicMethod,最后再总结两者的区别。

    1、MethodBuilder 介绍:

    MethodBuilder 是一个强大的工具,用于在动态程序集中创建方法。

    如果你需要构建整个类型(包括字段、属性、方法等),那么按流程:

    首先你需要创建一个动态程序集(AssemblyBuilder),

    然后在其中创建一个模块(ModuleBuilder),

    最后再创建一个或多个类型(TypeBuilder)。

    而要在这些类型中创建方法,就可以使用 MethodBuilder

    其关键特点:

    • 绑定到类型MethodBuilder 创建的方法是属于某个类型的一部分,因此只能通过该类型的实例或静态引用来调用。
    • 方法签名:需要指定方法名称、参数类型和返回类型。
    • IL代码生成:需要手动编写IL代码。

    2、MethodBuilder 代码:定义方法

    正如上文所说,MethodBuilder 的使用,需要定义整个程序集上下文。

    因此我们先编写一下共用部分代码,同样用于( .NET 版本生成程序集,以便对照生成代码):

    复制代码
    AssemblyName assName = new AssemblyName("myAssembly") { Version = new Version("1.1.1.2") };
    AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(assName, AssemblyBuilderAccess.RunAndSave);
    ModuleBuilder mb = ab.DefineDynamicModule("myModule", "b.dll");
    TypeBuilder tb = mb.DefineType("MyNameSpace.MyClass", TypeAttributes.Public | TypeAttributes.Class);
    
    
    //定义方法......
    
    
    tb.CreateType();
    ab.Save("b.dll");
    复制代码

    方法定义的过程:

    1、通过 TypeBuilder 的 DefineMethod 来定义方法:MethodBuilder methodBuilder = tb.DefineMethod("方法名",......);
    2、通过构造函数,可以设定方法定义的参数;
    3、也可以后面再通过 MethodBuilder 实例 的 SetXXX 及  系列来定义参数。

    下面示例展示方法的定义:

    A、定义实例方法:使用简单参数

    复制代码
     //定义实例方法:通过构造函数指定方法修饰符:Public、返回值:typeof(string)、参数类型:typeof(int),typeof(string)
     MethodBuilder methodBuilder = tb.DefineMethod("MyMethod", MethodAttributes.Public, typeof(string), new Type[] { typeof(int), typeof(string) });
    //定义参数的名称:指定参数名称:id,name methodBuilder.DefineParameter(
    1, ParameterAttributes.None, "id"); methodBuilder.DefineParameter(2, ParameterAttributes.None, "name"); //用IL编写方法实现 var il = methodBuilder.GetILGenerator(); il.EmitWriteLine("hello world!"); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ret);
    复制代码
    对照生成的代码:

    注意事项:

    1:DefineParameter 对参数的索引从1开始(对实例方法或静态方法都一样)。
    2:IL 构建代码时:实例方法下 Ldarg_0 是 this 自身,静态方法下 Ldarg_0 是id参数。

    B、定义静态方法:参数进阶【包括ref、out、指针】大杂绘

    复制代码
    //定义静态方法
    MethodBuilder methodBuilder = tb.DefineMethod("MyMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(object), new Type[] { typeof(int).MakeByRefType(), typeof(string).MakePointerType() });
    methodBuilder.DefineParameter(1, ParameterAttributes.None, "id");
    methodBuilder.DefineParameter(2, ParameterAttributes.Out | ParameterAttributes.Optional, "name");
    
    var il = methodBuilder.GetILGenerator();
    il.EmitWriteLine("hello world!");
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ret);
    复制代码

    对照生成的代码:

    简要说明:

    1、ref 参数定义:在构造函数类型用:typeof(int).MakeByRefType()
    2、指针 参数定义:在构造函数类型用:typeof(int).MakePointerType()
    3、out 参数定义:在 DefineParameter 方法的 ParameterAttributes.Out 参数指定。
    注意事项:在静态方法中,IL 代码 Ldarg_0 指向第一个参数 id。

    C、定义方法:添加特性

     // 添加自定义特性
     ConstructorInfo attributeConstructor = typeof(AssemblyTitleAttribute).GetConstructor(new Type[] { typeof(string) });
     CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder(attributeConstructor, new object[] { "ExampleAttribute" });
     methodBuilder.SetCustomAttribute(attributeBuilder);

    对照生成的代码:

    3、MethodBuilder 代码:方法的动态调用

    对于运行时生成的动态方法实现动态调用,可以通过反射调用。

    下面演示(将方法变更为实例方法,使用反射调用):

    通过获取类的定义Type classType,然后Type.GetMethod获取方法进行调用,并传进创建的实例作为参数之一。

    当然,如果方法调用频率很高,要更进一步,也可以为 MethodInfo 创建委托,来实现更高效率的调用。 

    然而:

    Delegate.CreateDelegate(......) 只支持从静态方法转委托方法,不支持实例方法。 
    
    所以:
    
    默认是不能实现对实例 MethodInfo 转委托的高效调用的。

    但,那是默认,经过本人的百日创新,结合 DynamicMethod,还是有办法实现的。

    实现方式可以参考本人的开源框架 Taurus.MVC 中关于 Deletgate 的相关实现。

    既然提到了 DynamicMethod,那下面就开始介绍它了。

    4、DynamicMethod 介绍:

    DynamicMethod 则更加灵活。它允许在运行时生成和执行方法,而无需创建动态程序集或动态类型来容纳该方法。

    这意味着你可以直接生成和执行少量代码,而不必担心整个类型的构建。

    关键特点:

    • 不绑定到类型DynamicMethod 不属于特定的类型,因此可以在任何上下文中调用。
    • 性能:通常比 MethodBuilder 更高效。

    5、DynamicMethod 代码:

    复制代码
     //创建动态方法
     DynamicMethod dynamicMethod = new DynamicMethod("MyMethod", typeof(void), null);
     var il = dynamicMethod.GetILGenerator();
     il.EmitWriteLine("hello world!");
     il.Emit(OpCodes.Ret);
    
     //创建调用委托
     var deletegateMethod = dynamicMethod.CreateDelegate(typeof(Action)) as Action;
     //执行委托
     deletegateMethod();
    复制代码

    执行结果:

    简要说明:

    1、DynamicMethod 拥有的基础定义方法和 MethodBuilder 基本一致。
    2、DynamicMethod 动态方法关注点是运行时执行,因此,可以省略很多参数的定义,只需要定义最简单的方法名、返回值、输入参数。
    3、DynamicMethod 默认创建的静态方法,因此,它可以默认拥有 CreateDelegate 来实现高效调用。

    6、DynamicMethod 与 MethodBuilder 两者的区别:

    在C#中,MethodBuilderDynamicMethod都属于反射发射(Reflection.Emit)的一部分,用于在运行时生成和定义方法。

    让我们来简要介绍一下它们之间的区别:

    1. DynamicMethod:

      • DynamicMethod类允许在运行时生成和执行方法,而无需创建动态程序集或动态类型来容纳该方法。
      • 由即时(JIT)编译器生成的可执行代码在DynamicMethod对象被回收时也会被回收。
      • 动态方法是生成和执行少量代码的最有效方式。
      • 如果需要动态创建一个或多个方法,应使用 DynamicMethod
    2. MethodBuilder:

      • MethodBuilder用于在动态程序集中创建方法。
      • 如果要创建整个类型(包括字段、属性、方法等),则需要先创建一个动态程序集(AssemblyBuilder),然后在其中创建一个模块(ModuleBuilder),最后再创建一个或多个类型(TypeBuilder)。
      • 若要在这些类型中创建方法,可以使用MethodBuilder

    通俗的讲人话即是:

    复制代码
    1、如果是生成动态程序集,包括创建动态类,那么使用 MethodBuilder。
    
    2、如果只是定义动态方法供调用,使用 DynamicMethod,因为它不用定义整个程序集,直接起手就是方法。
    
    3、使用委托调用方法:MethodBuilder 和 DynamicMethod 都支持,但 DynamicMethod 直接提供 CreateDelegate 方法,方便起手调用。
    复制代码

    总结:

    在本文的第五部分中,我们深入探讨了 .NET Emit 中动态生成方法的两种方式:MethodBuilder 和 DynamicMethod。

    通过 MethodBuilder,我们可以在运行时动态创建和定义方法,为其添加参数、自定义属性等元数据信息。

    而 DynamicMethod 则提供了一种更灵活的动态方法生成方式,特别适合于需要高性能的动态代码生成场景。

    在本篇教程中,我们学习了如何使用 MethodBuilder 来创建动态方法,并且演示了如何定义带有引用参数的动态方法以及如何向动态方法添加自定义属性。

    这些内容对于理解动态方法的创建和扩展具有重要意义。

    在下一篇中,我们将重点讲述IL语言,IL(Intermediate Language)是.NET平台上的一种中间语言,它是由C#、VB.NET等高级语言编译成的一种低级语言表示形式。

    我们将会详细介绍IL语言的基本结构、指令集、堆栈操作等内容,帮助读者更深入地理解.NET动态方法的内部实现和执行过程。

    通过深入了解IL语言,读者将能够更好地掌握.NET平台上动态代码生成的技术,并且能够对IL代码进行优化和调试,从而更好地应用于实际的软件开发项目中。

    敬请期待下一篇教程的发布,让我们一起探究IL语言的奥秘吧!

  • 相关阅读:
    k8s优雅停服
    最易/难学习的编程语言榜单出炉,C++最难学?
    JDK核心JAVA源码解析(8) - 自动封箱拆箱与效率的思考
    关于OAuth2.0 Authorization Code + PKCE flow在原生客户端(Native App)下集成的一点思考
    通过点击CheckBox实现背景变换小案例
    论文阅读:《Ad Click Prediction: a View from the Trenches》
    嵌入式开发:ST-LINK V2.1仿真器,Type-C接口
    Spring Boot中的常用注解
    基于JAVA的物流信息管理平台【数据库设计、源码、开题报告】
    【c++】stringstream基础:实现数据类型转换和字符串分割
  • 原文地址:https://www.cnblogs.com/cyq1162/p/18094636