反射发出--Emit

简介:

"反射"和"反射发出(Emit)"的关系

相信反射大家都不陌生,我也曾写过关于反射的文章,大家有兴趣可以看看,但是今天要说的不是"反射"而是"反射发出(Emit)"。我们知道反射的主要功能是获得对象的信息、调用对象的方法等;而反射发出的主要功能是动态的创建对象。那么是不是二者就没有关系呢?事实上二者不经有关系而且关系十分密切。从命名空间我们就可以看出来,反射处于System.Reflection 命名空间下,而反射发出Emit处于System.Reflection.Emit 命名空间下,它的功能虽然主要是动态创建对象,但是很多反射能够完成的工作它也可以完成而且速度更快(实现当然较反射而言要麻烦一些)。也就是说反射主要用到对象已经存在的情况下,而反射发出主要用到对象并不存在等情况下(而利用代码动态的构建对象)。

"反射发出(Emit)"动态构建对象的原理

我们既然知道了Emit可以动态创建对象,那么Emit是如何做到呢?这就必须要提到MSIL,它是类似于java虚拟机的一种无关于CPU的中间语言,也就是说不管你是用什么语言只要最终生成IL,那么.Net就可以执行(这也是.Net上为什么能够运行C#、VB、F#等多种语言的原因)。利用Emit之所以能够动态构建对象,也是因为它可以直接生成IL,这样一来我们当然也就可以动态创建对象了,而且速度几乎接近于直接运行已有代码。

"反射发出(Emit)"的用途

Emit最典型的应用就是:增强反射性能、动态代理(AOP)、ORM实现等。

"反射发出(Emit)"的实现

使用Emit的步骤几乎是固定的,网上很多文章都列举的很清楚,这里我们就先看代码吧。假设现在我准备构建一个这样的对象:

 

复制代码

    
    
1 using System;
2   using System.Collections.Generic;
3   using System.Linq;
4   using System.Text;
5 namespace MyEmit
6 {
7 public class MyClass
8 {
9 private double a = 0 ;
10 private double b = 0 ;
11 public MyClass()
12 {
13 }
14 public MyClass( double a, double b)
15 {
16 this .a = a;
17 this .b = b;
18 }
19
20 public double Sum()
21 {
22 return a + b;
23 }
24 public double A
25 {
26 get
27 {
28 return a;
29 }
30 set
31 {
32 a = value;
33 }
34 }
35 }
36 }
37
复制代码

 

 

上面的代码我们直接书写然后在vs中编译几乎是再简单不过的事情了,可是它包括了成员变量、构造函数、属性、方法等多个对象常用元素,最为一个例子应该说是比较有代表性的了。下面就来看看我们如何使用代码动态构建来代替手工书写编译的过程吧:

 

复制代码

    
    
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Reflection;
6 using System.Reflection.Emit;
7 namespace Emit_ConsoleApplication
8 {
9 class Program
10 {
11 static void Main( string [] args)
12 {
13 // 构建程序集
14 AssemblyName aName = new AssemblyName( " MyEmit " );
15 AppDomain aDomain = AppDomain.CurrentDomain; // 应用程序域,这里设为当前域
16 AssemblyBuilder aBuidler = aDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run);
17 // 定义模块
18 ModuleBuilder mBuidler = aBuidler.DefineDynamicModule( " MyModule " );
19 // 创建类型(其实就是一个类)
20 TypeBuilder tBuidler = mBuidler.DefineType( " MyEmit.MyClass " , TypeAttributes.Public | TypeAttributes.Class);
21 // --实现类-- //
22 // 定义两个私有字段
23 FieldBuilder fb_a = tBuidler.DefineField( " a " , typeof ( double ), FieldAttributes.Private);
24 FieldBuilder fb_b = tBuidler.DefineField( " b " , typeof ( double ), FieldAttributes.Private);
25 // 为私有变量赋值
26 fb_a.SetConstant(( double ) 0 );
27 fb_b.SetConstant(( double ) 0 );
28 // 定义构造函数
29 ConstructorBuilder ctstBuilderWithoutParameters = tBuidler.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, null ); // 无参数构造函数
30 ILGenerator ctstWithoutParametersGenerator = ctstBuilderWithoutParameters.GetILGenerator();
31 ctstWithoutParametersGenerator.Emit(OpCodes.Ret);
32 ConstructorBuilder ctstBuilder = tBuidler.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new Type[] { typeof ( double ), typeof ( double ) });
33 ILGenerator ctstGenerator = ctstBuilder.GetILGenerator();
34 ctstGenerator.Emit(OpCodes.Ldarg_0);
35 ctstGenerator.Emit(OpCodes.Ldarg_1);
36 ctstGenerator.Emit(OpCodes.Stfld, fb_a); // 给字段赋值
37 ctstGenerator.Emit(OpCodes.Ldarg_0);
38 ctstGenerator.Emit(OpCodes.Ldarg_2);
39 ctstGenerator.Emit(OpCodes.Stfld, fb_b);
40
41 ctstGenerator.Emit(OpCodes.Ret);
42 // 定义属性
43 PropertyBuilder pro_A = tBuidler.DefineProperty( " A " , PropertyAttributes.None, typeof ( double ), null );
44 MethodBuilder mBuidlerGetA = tBuidler.DefineMethod( " get " , MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof ( double ), Type.EmptyTypes); // 属性的get方法
45 ILGenerator getAGenerator = mBuidlerGetA.GetILGenerator();
46 getAGenerator.Emit(OpCodes.Ldarg_0);
47 getAGenerator.Emit(OpCodes.Ldfld,fb_a);
48 getAGenerator.Emit(OpCodes.Ret);
49 MethodBuilder mBuidlerSetA = tBuidler.DefineMethod( " set " , MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null , new Type[]{ typeof ( double )});
50 ILGenerator setAGenerator = mBuidlerSetA.GetILGenerator();
51 setAGenerator.Emit(OpCodes.Ldarg_0);
52 setAGenerator.Emit(OpCodes.Ldarg_1);
53 setAGenerator.Emit(OpCodes.Ldfld, fb_a);
54 setAGenerator.Emit(OpCodes.Ret);
55 // 定义方法
56 MethodBuilder mtdBuidler = tBuidler.DefineMethod( " Sum " ,MethodAttributes.Public, typeof ( double ), null );
57 ILGenerator mtdGenerator = mtdBuidler.GetILGenerator();
58 mtdGenerator.Emit(OpCodes.Ldarg_0);
59 mtdGenerator.Emit(OpCodes.Ldfld,fb_a);
60 mtdGenerator.Emit(OpCodes.Ldarg_0);
61 mtdGenerator.Emit(OpCodes.Ldfld,fb_b);
62 mtdGenerator.Emit(OpCodes.Add);
63 mtdGenerator.Emit(OpCodes.Ret);
64
65 // 创建引用、调用方法
66 Type MyClass = tBuidler.CreateType();
67 object myInstance = Activator.CreateInstance(MyClass, new object []{ 1.1 , 2.2 }); // 实例化
68 // object result = MyClass.InvokeMember("Add", BindingFlags.InvokeMethod, null, myInstance, new object[]{1.2,2.3});
69 object result = MyClass.GetMethod( " Sum " ).Invoke(myInstance, null );
70 Console.WriteLine( " this result is {0} " , result.ToString());
71 }
72 }
73 }
74
复制代码

 

 

上面的注释已经说明了使用Emit的常用步骤,一步步书写就可以了,但是关键就是方法实现部分的代码不是太容易,但我们上面已经说过使用Emit的过程可以看成是构建IL的过程,如果我们能够看到我们想要构建的对象的IL代码再用Emit来书写就容易多了。这里就向大家推荐一个工具—reflector,我们可以先将上面MyEmit.MyClass这个类编译成dll或exe,然后使用reflector查看器相应的IL代码,然后在书写Emit的时候(主要是方法体的实现)对照IL来书写就容易多了。例如上面Sum方法的IL代码:

 

复制代码

    
    
1 .method public hidebysig instance float64 Sum() cil managed
2 {
3 .maxstack 2
4 .locals init (
5 [ 0 ] float64 CS$ 1 $ 0000 )
6 L_0000: nop
7 L_0001: ldarg. 0
8 L_0002: ldfld float64 MyEmit.MyClass::a
9 L_0007: ldarg. 0
10 L_0008: ldfld float64 MyEmit.MyClass::b
11 L_000d: add
12 L_000e: stloc. 0
13 L_000f: br.s L_0011
14 L_0011: ldloc. 0
15 L_0012: ret
16 }
复制代码

 

 

当然,reflector反编译过的IL可以帮助我们,但是只能够作为参考,中间需要增减相关代码,如果想要彻底的了解Emit中IL的书写还需要多加学习。另外,上面我们的程序集、模块、构造函数、属性、方法等的创建都是使用Emit来实现的,但是最后对于类的实例化和方法的调用却是典型的反射应用,当然这些我们仍然可以使用Emit来做到,而且网上也有很多的介绍,如果今后有时间我会再和大家一块学习的,今天就先到这里吧。

知识共享许可协议 本作品采用知识共享署名 2.5 中国大陆许可协议进行许可,欢迎转载,演绎或用于商业目的。但转载请注明来自崔江涛(KenshinCui),并包含相关链接。
目录
相关文章
computed【计算属性】watch【监听】methods【方法】的区别
computed【计算属性】watch【监听】methods【方法】的区别
|
6天前
|
JavaScript 前端开发
计算属性和 watch 监听函数的回调函数可以异步执行吗?
【10月更文挑战第23天】总的来说,虽然计算属性和监听函数的回调函数通常是同步执行的,但在特定情况下可以进行异步操作。在实际应用中,要根据具体的需求和场景来合理选择是否使用异步执行,并注意处理好异步操作的结果和状态,以确保应用的正常运行和性能优化。
|
6天前
|
JavaScript
computed 计算属性和 watch 监听函数的执行顺序
【10月更文挑战第23天】理解`computed`计算属性和`watch`监听函数的执行顺序是 Vue.js 开发中的一个重要知识点,它有助于我们更好地处理数据的变化和响应,确保应用的正常运行和性能优化。
|
6月前
|
iOS开发
Object c事件链传递
Object c事件链传递
58 1
|
6月前
当监听的属性是对象的引用时,`watch`选项是否会触发监听?
当监听的属性是对象的引用时,`watch`选项是否会触发监听?
39 2
|
6月前
|
JavaScript
Vue中子组件单个双向绑定发送事件使用input,多个使用 update:变量名;父组件接收时,v-model 接收单个,.sync 接收多个(vue3中使用v-model:name的写法)
Vue中子组件单个双向绑定发送事件使用input,多个使用 update:变量名;父组件接收时,v-model 接收单个,.sync 接收多个(vue3中使用v-model:name的写法)
|
JavaScript 前端开发
侦听属性 watch
侦听属性 watch
|
监控
利用Object.defineProperty挂接set钩子,监控对象属性的修改事件
利用Object.defineProperty挂接set钩子,监控对象属性的修改事件
145 0
利用Object.defineProperty挂接set钩子,监控对象属性的修改事件
|
JavaScript 前端开发 数据安全/隐私保护
JavaScript之EventListener事件的传递顺序--冒泡和捕获传播
演示监听事件的传播顺序以及如何阻止这种传播。冒泡和捕获传播
1221 0