Emit学习-基础篇-基本概念介绍

  之前的Hello World例子应该已经让我们对Emit有了一个模糊的了解,那么Emit到底是什么样一个东西,他又能实现些什么功能呢?昨天查了点资料,大致总结了下,由于才开始学习肯定有不完善的地方,希望大家能够批评指正。

1.       什么是反射发出(Reflection Emit

Emit应该是属于反射中的一个比较高级的功能,说到反射大家应该都不陌生,反射是在运行时发现对象的相关信息,并且执行这些对象(创建对象实例,执行对象上的方法)。这个功能是由.NETSystem.Reflection命名空间的类所提供的。简单的说,它们不仅允许你浏览一个程序集暴露的类、方法、属性和字段,而且还允许你创建一个类型的实例以及执行这些类型上的方法(调用成员)。这些特性对于在运行时对象发现,已经很了不起了,但.NET的反射机制并没有到此结束。反射还允许你在运行时构建一个程序集,并且可以创建全新的类型。这就是反射发出(reflection emit)。

使用Emit可以从零开始,动态的构造程序集和类型,在需要时动态的生成代码,提高程序的灵活性。有了这些功能,我们可以用其来实现一些典型的应用,如:

l  动态代理(AOP);

l  减少反射的性能损失(Dynamic Method等);

l  ORM的实现;

l  工具及IDE插件的开发;

l  公共代码安全模块的开发。

2.       使用Emit的完整流程

使用Emit一般包括以下步骤:

1)        创建一个新的程序集(可以选择存在与内存中或者持久化到硬盘);

2)        在程序集内创建一个模块;

3)        在模块内创建动态类;

4)        给动态类添加动态方法、属性、事件,等;

5)        生成相关的IL代码;

6)        返回创建出来的类型或持久化到硬盘中。

当然如果你只是想要创建一个Dynamic Method 那么可以直接使用之前HelloWorld例子中使用的DynamicMethod类来创建一个动态方法,并在构造函数时传入它所依附的类或者模块。看了这个流程,相信大家已经对用使用Emit来创建动态类型的过程有了一个直观的认识,下面我们就通过实现一个求斐波那契数列的类来加深对这一流程的了解。

在开始我们的例子之前,先给大家介绍一款反编译软件Reflector使用这个软件可以给我们编写IL代码提供很大的帮助。

接下来我们按照上面所说的流程来创建我们的斐波那契类:

第一步:构建程序集

要构建一个动态的程序集,我们需要创建一个AssemblyBuilder对象,AssemblyBuilder类是整个反射发出工作的基础,它为我们提供了动态构造程序集的入口。要创建一个AssemblyBuilder对象,需要使用AppDomainDefineDynamicAssembly方法,该方法包括两个最基本的参数:AssemblyNameAssemblyBuilderAccess前者用来唯一标识一个程序集,后者用来表示动态程序集的访问方式,有如下的成员:

成员名称

说明

Run

表示可以执行但不能保存此动态程序集。

Save

表示可以保存但不能执行此动态程序集。

RunAndSave

表示可以执行并保存此动态程序集。

ReflectionOnly

表示在只反射上下文中加载动态程序集,且不能执行此程序集。

在这里我们选择使用RunAndSave,完整的代码如下:

ContractedBlock.gifExpandedBlockStart.gifStep 1 构建程序集
#region Step 1 构建程序集
//创建程序集名
AssemblyName asmName = new AssemblyName("EmitExamples.DynamicFibonacci");

//获取程序集所在的应用程序域
//你也可以选择用AppDomain.CreateDomain方法创建一个新的应用程序域
//这里选择当前的应用程序域
AppDomain domain = AppDomain.CurrentDomain;

//实例化一个AssemblyBuilder对象来实现动态程序集的构建
AssemblyBuilder assemblyBuilder = domain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.RunAndSave);
#endregion

第二步:定义模块(Module

与第一步类似,要定一个动态模块,我们需要创建一个ModuleBuilder对象,通过AssemblyBuilder对象的DefineDynamicModule方法,需要传入模块的名字(如果要持久化到硬盘,那么还需要传入要保存的文件的名字,这里就是我们的程序集名),这里我们使用程序集名作为模块名字:

ContractedBlock.gifExpandedBlockStart.gifStep 2 定义模块
#region Step 2 定义模块
ModuleBuilder moduleBuilder 
= assemblyBuilder.DefineDynamicModule(name, asmFileName);

      第三部:创建一个动态类型

这个时候恐怕我不说你也已经知道了,对,现在我们就是要用ModuleBuilder来创建一个TypeBuilder的对象,如下:

ContractedBlock.gifExpandedBlockStart.gifStep 3 定义类型
#region Step 3 定义类型
TypeBuilder typeBuilder 
= moduleBuilder.DefineType("EmitExamples.DynamicFibonacci", TypeAttributes.Public);
#endregion

这里EmitExamples表示名字空间,DynamicFibonacci是类的名字,TypeAttributes表示类的属性,可以按照实际需要进行组合。

第四步:定义方法

到这里为止,我们的准备工作已经差不多了,下面要开始真正的大展拳脚啦!

我们先来看一下我们接下来要实现的动态类C#代码的实现,然后再以这为目标进行动态构建:

ContractedBlock.gifExpandedBlockStart.gifFibonacci
public class Fibonacci
{
    
public int Calc(int num)
    {
        
if (num == 1 || num == 2)
        {
            
return 1;
        }
        
else
        {
            
return Calc(num - 1+ Calc(num - 2);
        }
    }
}

OK,从上面的代码可以看出我们需要创建一个名为CalcPublic方法,它具有一个Int32型的传入参数和返回值。同样的,我们使用TypeBuilderDefineMethod方法来创建这样一个MethodBuilder,如下:

ContractedBlock.gifExpandedBlockStart.gifStep 4 定义方法
#region Step 4 定义方法

MethodBuilder methodBuilder 
= typeBuilder.DefineMethod(
    
"Calc"
    MethodAttributes.Public, 
    
typeof(Int32), 
    
new Type[] { typeof(Int32) });

#endregion

DefineMethod方法的四个参数分别是函数名,修饰符,返回值类型,传入参数的类型数组。

第五步:实现方法

现在就要为之前创建的Calc方法添加对应的IL代码了,这对我们这些新手来说这就显的有点无从入手来了,不过没关系,还记得我之前提到的那个反编译工具吗?现在就是它发挥作用的时候了,我们用它来反编译之前写的Fibonacci类,看看自动生成的IL代码是什么样的,结果如下:

ContractedBlock.gifExpandedBlockStart.gifIL
.method public hidebysig instance int32 Calc(int32 num) cil managed
{
    .maxstack 
4
    .locals init (
        [
0] int32 CS$1$0000,
        [
1bool CS$4$0001)
    L_0000: nop 
    L_0001: ldarg.
1 
    L_0002: ldc.i4.
1 
    L_0003: beq.s L_000e
    L_0005: ldarg.
1 
    L_0006: ldc.i4.
2 
    L_0007: ceq 
    L_0009: ldc.i4.
0 
    L_000a: ceq 
    L_000c: br.s L_000f
    L_000e: ldc.i4.
0 
    L_000f: stloc.
1 
    L_0010: ldloc.
1 
    L_0011: brtrue.s L_0018
    L_0013: nop 
    L_0014: ldc.i4.
1 
    L_0015: stloc.
0 
    L_0016: br.s L_002f
    L_0018: nop 
    L_0019: ldarg.
0 
    L_001a: ldarg.
1 
    L_001b: ldc.i4.
1 
    L_001c: sub 
    L_001d: call instance int32 EmitExamples.Fibonacci::Calc(int32)
    L_0022: ldarg.
0 
    L_0023: ldarg.
1 
    L_0024: ldc.i4.
2 
    L_0025: sub 
    L_0026: call instance int32 EmitExamples.Fibonacci::Calc(int32)
    L_002b: add 
    L_002c: stloc.
0 
    L_002d: br.s L_002f
    L_002f: ldloc.
0 
    L_0030: ret 
}

我们来对上面的IL代码进行分析:

l  L_0000L_0003是加载参数一、加载整数1,然后判断两者是否相等,如果相等则跳转到L_000e继续执行;

l  L_0005L_000e是加载参数一、加载整数2,然后判断两者是否相等,如果相等则将整数1送到堆栈上,否则将整数0送到堆栈上;然后再加载整数0,用之前比较的结果和0进行比较,如果相等则将整数1送到堆栈上,否则将整数0送到堆栈上;这个时侯,如果传入的参数是2那么现在堆栈上的数字就是两个0,两者相等,那么跳转到L_000f继续执行,反之就继续执行,加载数字0到堆栈上(是不是感觉很复杂,没关系,我们一会对其进行优化);

l  L_000fL_0016是判断之前判断的返回值,也就是说如果传入的参数是1或者2,那么就将局部变量0的值设为1,然后跳转到L_002f执行;反之就从L_0018开始执行;

l  L_0018L_002b是把参数0和参数1加载(注意:在非静态方法中,参数0表示其对自身所在类的示例的引用,相当于this),然后将参数1分别减去12后进行递归调用,并将结果相加,并把记过放到局部变量0中;

l  L_002dL_0030是加载局部变量0,并将结果返回。

有了之前分析的基础,我们可以将流程简化为如下步骤:

1)        如果传入的参数是1,跳转到第六步执行;

2)        如果传入的参数是2,跳转到第六步执行;

3)        将传入的参数减1,然后递归调用自身;

4)        将传入的参数减2,然后递归调用自身;

5)        将递归调用的结果相加,跳转到第七步执行;

6)        设置堆栈顶的值为1

7)        返回堆栈顶的元素作为结果。

然后我们就可以参照以上的反编译出来的IL代码,用Emit书写出对应的IL代码,具体代码如下:

ContractedBlock.gifExpandedBlockStart.gifStep 5 实现方法
#region Step 5 实现方法

ILGenerator calcIL 
= methodBuilder.GetILGenerator();

//定义标签lbReturn1,用来设置返回值为1
Label lbReturn1 = calcIL.DefineLabel();
//定义标签lbReturnResutl,用来返回最终结果
Label lbReturnResutl = calcIL.DefineLabel();

//加载参数1,和整数1,相比较,如果相等则设置返回值为1
calcIL.Emit(OpCodes.Ldarg_1);
calcIL.Emit(OpCodes.Ldc_I4_1);
calcIL.Emit(OpCodes.Beq_S, lbReturn1);

//加载参数1,和整数2,相比较,如果相等则设置返回值为1
calcIL.Emit(OpCodes.Ldarg_1);
calcIL.Emit(OpCodes.Ldc_I4_2);
calcIL.Emit(OpCodes.Beq_S, lbReturn1);

//加载参数0和1,将参数1减去1,递归调用自身
calcIL.Emit(OpCodes.Ldarg_0);
calcIL.Emit(OpCodes.Ldarg_1);
calcIL.Emit(OpCodes.Ldc_I4_1);
calcIL.Emit(OpCodes.Sub);
calcIL.Emit(OpCodes.Call, methodBuilder);

//加载参数0和1,将参数1减去2,递归调用自身
calcIL.Emit(OpCodes.Ldarg_0);
calcIL.Emit(OpCodes.Ldarg_1);
calcIL.Emit(OpCodes.Ldc_I4_2);
calcIL.Emit(OpCodes.Sub);
calcIL.Emit(OpCodes.Call, methodBuilder);

//将递归调用的结果相加,并返回
calcIL.Emit(OpCodes.Add);
calcIL.Emit(OpCodes.Br, lbReturnResutl);

//在这里创建标签lbReturn1
calcIL.MarkLabel(lbReturn1);
calcIL.Emit(OpCodes.Ldc_I4_1);

//在这里创建标签lbReturnResutl
calcIL.MarkLabel(lbReturnResutl);
calcIL.Emit(OpCodes.Ret);       

#endregion

第六步:创建类型,并持久化到硬盘

到上一步为止,我们已经完成了斐波那契类以及方法的完整创建,接下来就是收获的时候了,我们使用TypeBuilderCreateType方法完成最终的创建过程;最后使用AssemblyBuilder类的Save方法将程序集持久化到硬盘中,代码如下:

ContractedBlock.gifExpandedBlockStart.gifStep 6 收获
#region Step 6 收获

Type type 
= typeBuilder.CreateType();

assemblyBuilder.Save(asmFileName);

object ob = Activator.CreateInstance(type);

for (int i = 1; i < 10; i++)
{
    Console.WriteLine(type.GetMethod(
"Calc").Invoke(ob, new object[] { i }));
}

#endregion

 

这里使用Activator.CreateInstance方法创建了动态类型的一个实例,然后使用MethodInfoInvoke方法调用里里面的Calc方法,看起来需要通过多次反射,好像性能并不是很好,但其实我们完全可以用Emit来替代掉这两个方法,将反射带来的性能影响降到最低,这个将在以后讲到。最后在这里提供源程序的下载 Fibonacci

 

 

转载于:https://www.cnblogs.com/yingql/archive/2009/03/22/1418941.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/379717.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

The FreeRTOS Distribution(介绍、移植、类型定义)

1 Understand the FreeRTOS Distribution 1.1 Definition &#xff1a;FreeRTOS Port FreeRTOS目前可以在20种不同的编译器构建&#xff0c;并且可以在30多种不同的处理器架构上运行&#xff0c;每个受支持的编译器和处理器组合被认为是一个单独的FreeRTOS Port。 1.2 Build…

Eclipse项目左上角出现大红色感叹号怎么办?

出现大红色感叹号是因为环境不匹配 解决方法&#xff1a; 右击出现大红色感叹号的项目 点击 Libraries&#xff0c;将有叉号的给Remove掉 然后再点击 Add Library —> JRE System Library —> Next 勾选第二个即可 之后&#xff0c;就不会出现大红色感叹号了。

PCB---STM32最小系统制作过程

PCB 制作过程STM32核心模块连接外部电源晶振OSC_IN(8MHz)OSC32_IN(32.768MHz&#xff09;复位下载口BOOT模式电源模块添加功能UARTWKUPSTM32核心模块 这里我们以STM32F103C8T6为列&#xff0c;先将芯片的原理图放到原理图中 对于STM32&#xff0c;有几个模块是核心&#xff0…

FreeRTOS---堆内存管理(一)

FreeRTOS的堆内存管理简介动态内存分配及其与 FreeRTOS 的相关性动态内存分配选项内存分配方案Heap_1heap_2Heap_3Heap_4设置heap_4的起始地址Heap_5vPortDefineHeapRegions()堆相关的函数xPortGetFreeHeapSizexPortGetMinimumEverFreeHeapSizeMalloc调用失败的Hook函数这篇文章…

FreeRTOS--堆内存管理(二)

堆内存管理代码具体实现heap_1内存申请函数内存释放函数heap_2内存块内存堆初始化函数内存块插入函数内存申请函数判断是不是第一次申请内存开始分配内存内存释放函数heap_3heap_4内存堆初始化函数内存块插入函数heap_5上一篇文章说了FreeRTOS实现堆内存的原理&#xff0c;这一…

css中的node.js_在Node App中使用基本HTML,CSS和JavaScript

css中的node.jsYou may think this is not important, but it is!. As a beginner in node.js, most coding exercises are always server sided. 您可能认为这并不重要&#xff0c;但确实如此&#xff01; 作为node.js的初学者&#xff0c;大多数编码练习始终都是服务器端的。…

Binary String Matching(C++)

题目描述: Given two strings A and B, whose alphabet consist only ‘0’ and ‘1’. Your task is only to tell how many times does A appear as a substring of B? For example, the text string B is ‘1001110110’ while the pattern string A is ‘11’, you should…

VisualStudio2019配置OpenCV

VisualStudio2019配置OpenCV配置0x01 准备0x02 配置系统环境0x03 复制文件0x04 配置VisualStudio2019测试配置 0x01 准备 下载opencv&#xff0c;官网地址&#xff1a;https://opencv.org/releases/# 下载之后&#xff0c;自行安装 0x02 配置系统环境 找到高级系统设置 …

Visual Studio进行linux远程开发

目录准备工作创建一个项目配置远程项目准备工作 查看linux IP地址 安装了工具 sudo apt-get install openssh-server g gdb make ninja-build rsync zip开启ssh服务&#xff1a; sudo service ssh startVS2019按装了linux功能&#xff0c;如果没有&#xff0c;找到Visual S…

在给定总和K的二叉树中找到级别

Description: 描述&#xff1a; The article describes how to find the level in a binary tree with given sum K? This is an interview coding problem came in Samsung, Microsoft. 本文介绍了如何在给定总和K下在二叉树中找到级别 &#xff1f; 这是一个面试编码问题&a…

++i与i++的根本性区别(两个代码对比搞定)

首先来看i 代码如下&#xff1a; #include <stdio.h> #include <stdlib.h> int main() {int i0;int ai;printf("%d\n",a);printf("%d\n\n\n",i);return 0; }输出结果如下&#xff1a; 解释&#xff1a;i其实是两行代码的简写形式&#xff0c…

Python | 使用matplotlib.pyplot创建线图

Problem statement: Write a program in python (using matplotlib.pyplot) to create a line plot. 问题陈述&#xff1a;用python编写程序(使用matplotlib.pyplot)以创建线图。 Program: 程序&#xff1a; import matplotlib.pyplot as pltx [1,2,3,4,5,6,7,8,9,10]y [3,…

linux内核设计与实现---从内核出发

获取、编译、安装内核1 获取内核源码安装内核源代码何处安装源码使用补丁2 内核源码树3 编译内核减少编译的垃圾信息衍生多个编译作业安装内核启用指定内核作为引导4 内核开发的特点没有libc库头文件没有内存保护机制容积小而固定的栈1 获取内核源码 在linux内核官方网站http:…

linux内核设计与实现---进程管理

进程管理1 进程描述符及任务结构分配进程描述符进程描述符的存放进程状态设置当前进程状态进程上下文进程家族树2 进程创建写时拷贝fork()vfork()3 线程在Linux中的实现内核线程4 进程终结删除进程描述符孤儿进程造成的进退微谷5 小结进程的另一个名字叫做任务&#xff08;task…

生日蜡烛(蓝桥杯)

某君从某年开始每年都举办一次生日party&#xff0c;并且每次都要吹熄与年龄相同根数的蜡烛。 现在算起来&#xff0c;他一共吹熄了236根蜡烛。 请问&#xff0c;他从多少岁开始过生日party的&#xff1f; 请填写他开始过生日party的年龄数。 注意&#xff1a;你提交的应该是…

Linux内核设计与实现---进程调度

进程调度1 策略I/O消耗型和处理器消耗型的进程进程优先级时间片进程抢占2 Linux调度算法可执行队列优先级数组重新计算时间片schedule()计算优先级和时间片睡眠和唤醒负载平衡程序3 抢占和上下文切换用户抢占内核抢占4 实时5 与调度相关的系统调用与调度策略和优先级相关的系统…

ServletContext(核心内容)

什么是ServletContext对象 ServletContext代表是一个web应用的环境&#xff08;上下文&#xff09;对象&#xff0c;ServletContext对象 内部封装是该web应用的信息&#xff0c;ServletContext对象一个web应用只有一个 一个web应用有多个servlet对象 ServletContext对象的生…

【转载】[TC]飞船动画例子--《C高级实用程序设计》

【声明和备注】本例子属于转载来源于《C高级实用程序设计》&#xff08;王士元&#xff0c;清华大学出版社&#xff09;第11章&#xff0c;菜单设计与动画技术&#xff0c;第11.5节&#xff0c;一个动画例子。 本例讲解的是在一个繁星背景下&#xff0c;一个由经纬线组成的蓝色…

Linux内核设计与实现---系统调用

系统调用1 API、POSIX和C库2 系统调用系统调用号3 系统调用处理程序指定恰当的系统调用参数传递4 系统调用的实现参数验证5 系统调用上下文绑定一个系统调用的最后步骤从用户空间访问系统调用为什么不通过系统调用的方式实现1 API、POSIX和C库 API&#xff1a;应用编程接口。一…

手动去设置HTTP响应行、响应头、响应体

①手动去设置HTTP响应行中的状态码&#xff0c;这里用到了response的setStatus(int sc);这个方法 package com.itheima.line;import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpSer…