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.pswp.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…

notepad++节点_在C ++中删除链接列表的中间节点

notepad节点Given a single Linked List and we have to delete the middle the element of the Linked List. 给定一个链表&#xff0c;我们必须删除链表中间的元素。 If the length of the linked list is odd then delete (( n1)/2)th term of the linked list and if the…

SET ANSI_NULLS ON

指定在与 Null 值一起使用等于 () 和不等于 (<>) 比较运算符时采用符合 ISO 标准的行为。 当 SET ANSI_NULLS 为 ON 时&#xff0c;即使 column_name 中包含空值&#xff0c;使用 WHERE column_name NULL 的 SELECT 语句仍返回零行。即使 column_name 中包含非空值&…

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…

scala 随机生成整数_如何在Scala中以整数形式获取当前年份?

scala 随机生成整数In Scala programming language, there is an option for the programmer to use libraries of java because of its interoperability with java. 在Scala编程语言中&#xff0c;程序员可以选择使用Java库&#xff0c;因为它可以与Java互操作。 There are …

转载:glut.h 与 stdlib.h中 的exit()重定义问题的解决

遇到的问题&#xff0c;来自&#xff1a;http://blog.sina.com.cn/s/blog_629c53bd0100f5li.html 出现&#xff1a; c:\codeprogram\microsoft visual studio 10.0\vc\include\stdlib.h(353): error C2381: “exit”: 重定义&#xff1b;__declspec(noreturn) 不同1> c:\pro…

括号配对问题(C++栈)

题目描述: 现在&#xff0c;有一行括号序列&#xff0c;请你检查这行括号是否配对。 输入描述: 第一行输入一个数N&#xff08;0<N<100&#xff09;,表示有N组测试数据。后面的N行输入多组输入数据&#xff0c;每组输入数据都是一个字符串S(S的长度小于10000&#xff0c;…

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

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

python中生成随机整数_在Python中生成0到9之间的随机整数

python中生成随机整数Following are the few explanatory illustrations using different python modules, on how to generate random integers? Consider the scenario of generating the random numbers between 0 and 9 (both inclusive). 以下是使用不同的python模块的一…

愚人节恶搞网站谨防遭黑客攻击

金山毒霸云安全中心日前发出预警&#xff0c;在近期拦截的大量“挂马”、钓鱼等恶意网页中&#xff0c;与“愚人节”相关的&#xff0c;在近一周数量急剧增加。 愚人节将至&#xff0c;怎么整人好玩?近期许多恶搞网站、相关的网络论坛的流量不断攀升。金山毒霸云安全中心日前发…

JavaScript中的String()函数与示例

String()函数 (String() function) String() function is a predefined global function in JavaScript, it is used to convert an object to the string. String()函数是JavaScript中预定义的全局函数&#xff0c;用于将对象转换为字符串。 Example: 例&#xff1a; In thi…

ASCII码排序(C++)

题目描述: 输入三个字符&#xff08;可以重复&#xff09;后&#xff0c;按各字符的ASCII码从小到大的顺序输出这三个字符。 输入描述: 第一行输入一个数N,表示有N组测试数据。后面的N行输入多组数据&#xff0c;每组输入数据都是占一行&#xff0c;有三个字符组成&#xff0c;…

FreeRTOS--堆内存管理(二)

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

在查询的结果中添加自增列 两种方法

解决办法《一》&#xff1a; 在SQL Server数据库中表信息会用到Identity关键字来设置自增列。但是当有数据被删除的话&#xff0c;自增列就不连续了。如果想查询出这个表的信息&#xff0c;并添 加一列连续自增的ID&#xff0c;可用如下查询语句&#xff1a; SELECT Row_Nu…

一个非常简单的C#面试题

怎样实现对所有类可读但是在同一个assembly可写那&#xff1f; 答案&#xff1a; 同一个assembly namespace ClassLibrary1 { public class Class1 { public string Name { get; internal set; } } public class Class2 { public void GS() { Class1 cc new Class1(); cc.Name…

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…

由一次代码优化想到的Js 数据类型

引子&#xff1a; 上周三进行了代码优化&#xff0c;其中有一个很普遍的代码&#xff0c;例如&#xff1a; if(test "") {dothis();}else{dothat()} ----->可以简化为 !test ? dothis():dothat(); if(test "") {dothis()}     ----->可以简化为…

VisualStudio2019配置OpenCV

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