`

Emit学习之旅(2):创建常见元素—基础部分

    博客分类:
  • C#
阅读更多

上回已经用Emit创建了一个简单的类型。下面接着说说如何为动态创建的类添加实例成员,属性,方法等。

准备工作      这次来创建一个Student类。首先准备好大致框架:
AssemblyName assemName = new AssemblyName();
            assemName.Name = "EmitStudy2";

            AssemblyBuilder asmBuilder =
               AppDomain.CurrentDomain.DefineDynamicAssembly(assemName,
                  AssemblyBuilderAccess.RunAndSave);

            var mdlBldr = asmBuilder.DefineDynamicModule("EmitStudy2", "EmitStudy2.dll");
            var typeBldr = mdlBldr.DefineType("Sdudent", TypeAttributes.Public);

            //TODO:添加成员

           typeBldr.CreateType();
           asmBuilder.Save("EmitStudy2.dll");
 

 

一、定义实例变量

  Student包含以下实例成员:id(int),name(string)。下面就来一一创建它们。

  创建实例成员要通过TypeBuilder的DefineField方法来完成。该方法有以下形式:

  • DefineField(String, Type, FieldAttributes) :用给定的名称、属性和字段类型,向类型中添加新字段。
  • DefineField(String, Type, Type[], Type[], FieldAttributes):用给定的名称、属性、字段类型和自定义修饰符,向类型中添加新字段。

     一般情形下,前者使用的比较多。首先创建id:

var fldId = typeBldr.DefineField("id", typeof (int), FieldAttributes.Private);

 接着是name:

var fldName = typeBldr.DefineField("name", typeof(string), FieldAttributes.Private);

 这时您用reflector打开EmitStudy2.dll会发现Student已经拥有了我们想要的id,name,scores实例变量。

 

   二、定义属性

  Student类相应的也包含了三个简单属性:Id,Name。大家都知道,C#中的属性会被编译为相应的get_XXX或set_XXX的方法组。因此创建属性实质就是在创建方法。  

  创建方法通过TypeBuilder.DefineMethod完成。该方法重载形式较多,这里就不一一列出,感兴趣的请看这里 。还是以Id为例,我们为之创建GetId和SetId方法:

var methodGetId = typeBldr.DefineMethod("GetId", MethodAttributes.Public, typeof (int), null);
            var methodSetId = typeBldr.DefineMethod("SetId", MethodAttributes.Public, null, new Type[] {typeof (int)});

            var ilGetId = methodGetId.GetILGenerator();
            ilGetId.Emit(OpCodes.Ldarg_0);
            ilGetId.Emit(OpCodes.Ldfld, fldId);
            ilGetId.Emit(OpCodes.Ret);

            var ilSetId = methodSetId.GetILGenerator();
            ilSetId.Emit(OpCodes.Ldarg_0);
            ilSetId.Emit(OpCodes.Ldarg_1);
            ilSetId.Emit(OpCodes.Stfld,  fldId);

 这段代码定义了两个方法:GetId和SetId,但是这两个方法还没有真正和属性关联起来。首先,我们需要创建属性Id:

var prptId = typeBldr.DefineProperty("Id", PropertyAttributes.None, typeof (int), null);

 (在这里要说明的一点是,在很多DefineXXX方法中都包含返回类型和参数类型这两个属性,如果无返回值或者无参数直接传递null就可以了)

  创建了名称为Id的属性后,我们再将属性和get/set方法关联起来:

prptId.SetGetMethod(methodGetId);
prptId.SetSetMethod(methodSetId);

 另外两个属性类似。在上面创建get/set方法时提到了几个新的OpCodes:

  OpCodes.Ldarg_0:Ldarg是加载方法参数的意思。这里arg_0事实上是对当前对象的引用即this。因为类的实例方法(非静态方法)在调用时,this是会作为第一个参数传入的。

  OpCodes.Ldarg_1:当然就是参数列表的第一个参数了。

  OpCodes.Stfld:用新值替换在对象引用或指针的字段中存储的值。

          堆栈转换行为依次为: 1.将对象引用(或指针)推送到堆栈上。

                     2.将值推送到堆栈上。从堆栈中弹出该值和对象引用/指针;

                     3.用所提供的值替换对象中 field 的值。

           意思就是在往field存值时需要三个步骤:

          1.将对象引用(或指针)推送到堆栈上:ilSetId.Emit(OpCodes.Ldarg_0);=》加载this

          2.将值推送到堆栈上。从堆栈中弹出该值和对象引用/指针:ilSetId.Emit(OpCodes.Ldarg_1)=》将要存入field的值载入堆栈

          3.用所提供的值替换对象中 field 的值:ilSetId.Emit(OpCodes.Stfld,  fldId)=》设置field的值

  OpCodes.Ldfld:当然就是将指定field的值加载到堆栈上了。

  OpCodes的成员命名都是很规范的,ldXXX一般就是加载什么到堆栈,stXXX则是设置xxx的值,ldcXX则是加载常量xx到堆 栈,所以写代码时如果遇到不知道命令是什么就完全可以按照这种规则去推测。对于命令不知道如何用时去查查MSDN相应的堆栈转换规则是什么就一目了然了。

 

  三、创建方法

  在上一小节已经创建了几个方法,这里再大致描述下创建方法的流程:

  1.使用TypeBuilder.DefineMethod创建一个方法;

  2.调用 MethodBuilder.GetIlGenerator获取IL生成器

  3.写入自己需要的IL代码。

  我们在这里为Student添加一个ToString方法,我们要达到的目标如下:

public override string ToString()
{
    return string.Format("ID:{0} Name:{1}", this.id, this.name);
}

      在这里要注意的是string.Format产生的结果并不会被推送到堆栈上,而是会被存储到一个局部变量当中。只不过平时写C#代码并未体现出这一点而已。因此可以如下创建此方法:

 

var methodToString = typeBldr.DefineMethod("ToString", MethodAttributes.Virtual | MethodAttributes.Public,
                                                       typeof (string), null);
            var il = methodToString.GetILGenerator();
            var local = il.DeclareLocal(typeof (string));//创建一个局部变量
            il.Emit(OpCodes.Ldstr,"ID:{0} Name:{1}");
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldfld, fldId);
            il.Emit(OpCodes.Box, typeof(int));
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldfld, fldName);
            il.Emit(OpCodes.Call, typeof(string).GetMethod("Format", new Type[]{typeof(string), typeof(object), typeof(object)}));
            il.Emit(OpCodes.Stloc, local);
            il.Emit(OpCodes.Ldloc, local);
            il.Emit(OpCodes.Ret);

 这里有几个新元素:

  1.创建局部变量:使用ILGenerator.DeclareLocal完成

  2.OpCodes.Box:对值类型装箱,注意第二个参数为该值类型的类型。

  3.OpCodes.Stloc和OpCodes.Ldloc:设置局部变量的值和加载局部变量值到堆栈。

 

  到现在为止,我们的Student反编译后大致如下:

public class Sdudent
{
    // Fields
    private int id;
    private string name;

    // Methods
    public override string ToString()
    {
        return string.Format("ID:{0} Name:{1}", this.id, this.name);
    }

    // Properties
    public int Id
    {
        get
        {
            return this.id;
        }
        set
        {
            this.id = value;
        }
    }

    public string Name
    {
        get
        {
            return this.name;
        }
        set
        {
            this.name = value;
        }
    }
}
 

四.其他

  上面的实例都是关于实例成员的,对于静态成员呢?

  加载或设置静态实例字段会相应的变为:stsfld和ldsfld。而静态方法则由于不会将this作为第一个参数传递,因此ldarg_0就相应地变成了实际参数列表的第一个从参数。

 

 

 

 

 

 

 

分享到:
评论

相关推荐

    Emit学习之旅

    Emit学习之旅是一个深入探索C# Emit技术的教程,它为开发者提供了动态生成IL(Intermediate Language)代码的能力。Emit是.NET Framework的一部分,允许程序员在运行时构建和执行方法,这是实现元编程的重要工具。...

    Emit 学习资源汇总

    Emit,全称为Event Emitter,是Node.js中的一个核心模块,它在JavaScript世界中扮演着事件驱动的重要角色。在本文中,我们将深入探讨Emit的相关知识点,包括其原理、用法以及在实际开发中的应用。 事件驱动是Node....

    EMIT学习经典实例

    这是一个Visual Studio解决方案文件,它包含了一个或多个项目的集合,这些项目都是EMIT学习实例的一部分。通过打开这个.sln文件,用户可以查看和编译所有的示例项目。 4. **Harold.Net.Emit.Aop.Demo** AOP...

    Emit创建动态类

    在.NET框架中,C#提供了一种强大的编程技术,名为`System.Reflection.Emit`,它允许我们在运行时动态地创建类型、方法、属性等。标题"使用Emit创建动态类"揭示了这一技术的一个核心应用,即在程序执行过程中生成新的...

    Emit实现从URL或者表单中创建对象

    在IT行业中,Emit是一个非常重要的概念,尤其在JavaScript或TypeScript等动态语言中,它通常与事件处理和对象创建有关。在这个特定的场景中,"Emit实现从URL或者表单中创建对象"指的是利用Emit机制来解析URL或表单...

    Vue学习之旅之重点1:组件与数据传递

    在Vue.js的学习旅程中,组件化开发是其核心特性之一,极大地提高了代码的复用性和组织性。本篇文章将深入探讨Vue中的组件以及如何在组件间进行数据传递,旨在帮助初学者理解并掌握这一关键概念。 一、Vue组件 Vue...

    emit 类库 流畅api

    流畅API则是在Emit库的基础上,通过链式调用来构造IL指令,使得代码更易读。例如,我们可以创建一个简单的加法方法: ```csharp using System; using System.Reflection.Emit; public class DynamicMath { public...

    Emit实现动态代理

    2. **创建动态类型**:使用`System.TypeBuilder`类,我们可以创建一个新的类型。这个类型将继承自`System.Delegate`,并包含一个或多个与委托类型匹配的方法。 3. **定义方法**:使用`MethodBuilder`,为动态类型...

    Emit实现AOP示例

    纯手工打造Emit实现AOP private static void OverrideMethods(TypeBuilder tb, MethodInfo method) { if (!method.IsPublic|| !method.IsVirtual || IsObjectMethod(method)) return; Type[] paramTypes = ...

    .NET(C#):Emit创建异常处理的方法

    在这个场景中,我们关注的是如何使用Emit来创建包含异常处理逻辑的方法。异常处理在C#中是通过try-catch-finally语句实现的,而在IL级别,这些语句被转换为特定的IL指令。 首先,我们来理解Emit创建异常处理的基本...

    Emit常用Opcode指令使用方法

    除了在网上查资料之外学习MSIL另一个好方法就是.Net Reflector和ildasm.exe配合使用,.Net Reflector可以把Emit代码转换为普通C#代码,ildasm.exe可以把普通C#代码转换为MSIL,不会写某一功能的Emit代码就先把它的C#...

    Emit语法简单实现

    Emit代码示例,包括含参、不含参、静态、非静态等8个例子

    emit.js:JavaScript中的高效极简事件发射器

    emit.js JavaScript中的高效极简事件发射器。安装它可以与bower或npm一起使用: bower install emit.jsnpm install emit.js在HTML中包含emit.min.js ,并且在...用法创建一个事件发射器 var emitter = emit ( ) ;在上

    [干货][EMIT]千行代码实现代理式AOP+属性的自动装配

    【干货分享】代理式AOP(面向切面编程)与属性自动装配是现代软件开发中的重要技术,...记住,理论学习是基础,动手实践才是王道,只有真正动手编写代码,才能真正掌握这些高级技术。所以,不要只是看,更要动手尝试!

    $emit触发事件拿不到传递的参数.zip

    在Vue.js框架中,`$emit`是用来在组件实例中触发自定义事件的,通常用于父子组件间的通信。然而,有时候我们可能会遇到一个问题,即在父组件中通过`$on`监听子组件触发的事件时,无法接收到传递的参数。这个问题可能...

    emit-response-size:将响应大小作为事件发送

    安装$ npm install emit-response-size用法const resSize = require ( 'emit-response-size' )const httpNdjson = require ( 'http-ndjson' )const stdout = require ( 'stdout-stream' )const http = require ( '...

    使用MSIL采用Emit方式实现C#的代码生成与注入.rar

    Emit API提供了一种编程模型,允许程序员逐个指令地创建MSIL。这涉及到创建方法定义,定义局部变量,然后通过`ILGenerator`对象发出一系列的MSIL指令。例如,生成一个简单的加法函数可以通过以下步骤: 1. 创建`...

    j2ee class in emit

    Java Web 应用程序是J2EE的核心组成部分之一,它允许开发者创建能够运行在网络上的动态Web应用。这些应用通常由JSP(Java Server Pages)、Servlets和其他相关组件构成。 #### JSP - JavaServer Pages JSP是一种...

    emit-bindings:使用数据发射属性在 DOM 元素交互上发射事件

    使用数据发射属性在 DOM 元素交互上发射事件。 安装 npm install --save honeinc/emit-bindings 要直接在页面中包含预构建版本,请使用 build/emit-(version).js 或 build/emit-(version).min.js: < script ...

    vue2.x基础笔记(大部分)

    根据提供的文件内容,我们可以归纳出一系列关于 Vue 2.x 的基础知识点。这些知识点涵盖了 Vue 实例的创建、数据绑定、事件处理、自定义指令、组件、生命周期钩子以及实例属性等多个方面。 ### Vue 实例创建 Vue ...

Global site tag (gtag.js) - Google Analytics