玩转动态编译 - 高级篇:一,IL访问静态属性和字段
通用中间语言(Common Intermediate Language,简称CIL,发音为"sill"或"kill")是一种属于通用语言架构和.NET框架的低阶(lowest-level)的人类可读的编程语言。目标为.NET 框架的语言被编译成CIL,然后汇编成字节码。CIL类似一个面向对象的汇编语言,并且它是完全基于堆栈的。它运行在虚拟机上,其主要的语言有C#、Visual Basic .NET、C++/CLI以及 J♯。
在.NET语言的测试版中,CIL原本叫做微软中间语言,即Microsoft Intermediate Language,简称MSIL。由于C#和通用语言架构的标准化,在.Net开发平台下,所有语言(C#、VB.NET、J#、Managed C++)都会被编译为MSIL,再由CLR负责运行,字节码现在已经官方地成为了CIL。因此,CIL仍旧经常与MSIL相提并论,特别是那些.NET语言的老用户。
以上引用自维基百科
简单的来说,就是.NET在编译C#编码时会由一个“C#代码编译组件”将C#代码编译为IL中间码,然后再由另一套组件将IL中间码编译为字节码
虽然IL码很复杂,不过编写一些简单的问题也不大。
下面就由浅入深的举几个栗子
我们先编译一个简单的方法AAA
using System; using System.Collections.Generic; using System.Text; namespace blqw.IL.Demo { class Program { static void Main(string[] args) { } public object AAA() { return MyClass.Name; } } class MyClass { public static string Name { get; set; } } }
然后用reflector反射,看看生成的IL代码
先找到AAA方法,然后在语言的下拉框中选择IL
简单介绍一下每句IL的含义
.method public hidebysig instance object AAA() cil managed { .maxstack 1 .locals init ( [0] obje-0001ct CS$1$0000)//(以上的内容都不是我们需要关心的) L_0000: nop //如果修补操作码,则填充空间。尽管可能消耗处理周期,但未执行任何有意义的操作。(也就是说这个步骤是么意义的) L_0001: call string blqw.IL.Demo.MyClass::get_Name()//调用由传递的方法说明符指示的方法。(将返回值保存在堆栈中) L_0006: stloc.0 //从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中。(将刚才的返回值,保存在locals[0],也就是那个CS$1$0000中) L_0007: br.s L_0009//无条件地将控制转移到目标指令(类似于goto吧,跳转到下一行) L_0009: ldloc.0 //将索引 0 处的局部变量加载到计算堆栈上。 L_000a: ret // 从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上。(这句必须是方法中的最后一句,指示方法已经结束了,这时如果堆栈中有值,则返回值,没有则返回void) }
上面注释中括号里的都是我写的,括号外的是微软官方的注释
仔细看就会发现,其实编译好的IL生成了很多不必要的操作
比如L_0000.连微软自家都说这个操作是无意义的,尽可能消耗时间而已..
还有L_0006~L_0009.都是无用的;连起来看就会发现,它先把堆栈中的值保存到变量0中,然后goto到下一行,然后再把变量0的值加载到堆栈上,结果堆栈上原来是什么值还是什么值
所以真正有用的只有L_0001和L_000a,一句是执行方法,一句是返回
好了现在我们按照这个思路自己写一个IL,只有2句啊,超简单有木有!!!!!!
public static Func<object> ILTest() { //编译li动态方法 //1.声明动态方法对象DynamicMethod // 第一个参数是方法名,可以为空字符,不可以是null // 第二个参数是动态方法返回值类型 // 第三个参数是Type[],表示方法参数,如果没有参数可以是null // 第四个参数是声明逻辑关联类,可以让动态方法访问该类有权访问所有字段,包括逻辑关联类的私有字段 var dm = new DynamicMethod("", typeof(object), null, typeof(MyClass)); //2.声明il编译器 var il = dm.GetILGenerator(); //3.执行MyClass类的Name属性的Get方法 这句对应刚才的L_0001 il.Emit(OpCodes.Call, typeof(MyClass).GetProperty("Name").GetGetMethod()); //4.方法结束,这句对应刚才的L_000a il.Emit(OpCodes.Ret); //5.由il编译器创建指定类型的动态方法委托 return (Func<object>)dm.CreateDelegate(typeof(Func<object>)); }
去掉注释和方法声明,整个方法体才5行啊!
查看下运行结果
成功了!而且由于自己编写IL比微软生成好的代码少了好多,所以性能上更好
下面用一样的方法看看静态字段怎么访问
第一步,直接把MyClass的Name属性改为静态字段
class MyClass { public static string Name; }
查看反编译的IL代码
.method public hidebysig instance object AAA() cil managed { .maxstack 1 .locals init ( [0] object CS$1$0000) L_0000: nop L_0001: ldsfld string blqw.IL.Demo.MyClass::Name L_0006: stloc.0 L_0007: br.s L_0009 L_0009: ldloc.0 L_000a: ret }
好吧,只有L_0001不一样了
对应C#中的代码更变:
public static Func<object> ILTest() { //编译li动态方法 //1.声明动态方法对象DynamicMethod // 第一个参数是方法名,可以为空字符,不可以是null // 第二个参数是动态方法返回值类型 // 第三个参数是Type[],表示方法参数,如果没有参数可以是null // 第四个参数是声明逻辑关联类,可以让动态方法访问该类有权访问所有字段,包括逻辑关联类的私有字段 var dm = new DynamicMethod("", typeof(object), null, typeof(MyClass)); //2.声明il编译器 var il = dm.GetILGenerator(); //3.执行MyClass类的Name属性的Get方法 这句对应刚才的L_0001 //il.Emit(OpCodes.Call, typeof(MyClass).GetProperty("Name").GetGetMethod()); //OpCodes.Ldsfld : 将静态字段的值推送到计算堆栈上。 il.Emit(OpCodes.Ldsfld, typeof(MyClass).GetField("Name")); //4.方法结束,这句对应刚才的L_000a il.Emit(OpCodes.Ret); //5.由il编译器创建指定类型的动态方法委托 return (Func<object>)dm.CreateDelegate(typeof(Func<object>)); }
执行结果和刚才一样的就不贴了
其实在刚才的方法注释中我就已经提到了,第三个参数是"逻辑关联类"
这个可以理解成你把这个动态方法编译到哪个类中去了,在这个动态方法中可以访问逻辑关联类可以访问的所有对象
把刚才的栗子改为私有的试下?
class MyClass { static string Name; //这个方法是为了给Name赋值用 public static void SetName(string name) { Name = name; } }
public static Func<object> ILTest() { //编译li动态方法 //1.声明动态方法对象DynamicMethod // 第一个参数是方法名,可以为空字符,不可以是null // 第二个参数是动态方法返回值类型 // 第三个参数是Type[],表示方法参数,如果没有参数可以是null // 第四个参数是声明逻辑关联类,可以让动态方法访问该类有权访问所有字段,包括逻辑关联类的私有字段 var dm = new DynamicMethod("", typeof(object), null, typeof(MyClass)); //2.声明il编译器 var il = dm.GetILGenerator(); //3.执行MyClass类的Name属性的Get方法 这句对应刚才的L_0001 //il.Emit(OpCodes.Call, typeof(MyClass).GetProperty("Name").GetGetMethod()); //OpCodes.Ldsfld : 将静态字段的值推送到计算堆栈上。 il.Emit(OpCodes.Ldsfld, typeof(MyClass).GetField("Name", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static));//这里稍微改一下,反射到私有静态字段是要添加额外参数的 //4.方法结束,这句对应刚才的L_000a il.Emit(OpCodes.Ret); //5.由il编译器创建指定类型的动态方法委托 return (Func<object>)dm.CreateDelegate(typeof(Func<object>)); }
访问静态私有属性的方式基本雷同,反射私有属性的代码是:
typeof(MyClass).GetProperty("Name",System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static).GetGetMethod(true);
昨天是情人节,早早就上床了
嗯..别误会,出去玩了下,累了,所以睡得比较早
.本来应该把静态属性和字段的设置也写完的,真的没时间的,马上要上班去了
下一篇再说属性设置吧,同时吧类型转换一起将了,到时候还可以搞点小破坏哈~~
我发布的代码,没有任何版权,遵守WTFPL协议(如有引用,请遵守被引用代码的协议)
qq群:5946699 希望各位喜爱C#的朋友可以在这里交流学习,分享编程的心得和快乐