- 高级篇:二,IL设置静态属性,字段和类型转换
- 静态属性赋值
先来看 Reflector反射出的IL源码(感谢Moen的提示),这次用 Release模式编译,去掉那些无用的辅助指令
public void AAA(string s) { MyClass.Name = s; }
.method public hidebysig instance void AAA(string s) cil managed { .maxstack 8 //L_0000: ldarg.1 //这个是真正反射出的内容,但是理论上 这里应该是ldarg.0 //下面一行是我特意修改的,上面的现象我无法解释,请知道的朋友也可以告知一二 L_0000: ldarg.0//参数0,也就是string s这个参数推送的堆栈上 L_0001: stsfld string blqw.IL.Demo.MyClass::Name//使用当前堆栈上最近的一个参数,执行静态字段赋值指令, L_0006: ret //方法结束 }
- 小贴士:
每个操作系统都会从堆栈中获取指定数量的参数,比如上一篇中的静态字段/属性取值操作,这个操作不需要用到任何参数,比如执行一个方法,这个方法签名有几个参数,就需要提供几个参数,再比如执行一次比较,需要提供2个参数等等,每个操作需要的参数都是事先就指定好的
再来看C#中的代码:
public static Action<string> ILTest() { var dm = new DynamicMethod("", null, new[] { typeof(string) }, typeof(MyClass)); var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Stsfld, typeof(MyClass).GetField("Name")); il.Emit(OpCodes.Ret); return (Action<string>)dm.CreateDelegate(typeof(Action<string>)); }
进行一些测试
静态属性赋值
public static Action<string> ILTest() { var dm = new DynamicMethod("", null, new[] { typeof(string) }, typeof(MyClass)); var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, typeof(MyClass).GetProperty("Name").GetSetMethod()); il.Emit(OpCodes.Ret); return (Action<string>)dm.CreateDelegate(typeof(Action<string>)); }
- 类型转换
之前看的栗子都是方法参数类型和属性字段类型相同的情况下的赋值,那么如果类型需要转换呢?
比如这样: 将MyClass的Name属性的类型改为int
class MyClass { public static int Name { get; set; } }
然后把要生成的方法改为传入Object类型的参数进行复制,但是在调用的时候依然传入int
static void Main(string[] args) { var act = ILTest(); act(222); } public static Action<object> ILTest() { var dm = new DynamicMethod("", null, new[] { typeof(object) }, typeof(MyClass)); var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, typeof(MyClass).GetProperty("Name").GetSetMethod()); il.Emit(OpCodes.Ret); return (Action<object>)dm.CreateDelegate(typeof(Action<object>)); }
注意加了下划线的几个地方
再来看运行结果
111 50310368 请按任意键继续. . .
虽然程序没有抛出异常,但是结果确错了...
其实这个时候IL相当于生成了一个这样的方法
public void AAA(object i) { MyClass.Name = i; //编译器在这里就报错了 }
如果这个方法你是在VS中写的,那么在编译的时候编译器就告诉你这样写是错误,并且中断你的程序编译
编译器希望你改成这样
public void AAA(object i) { MyClass.Name = (int)i;//增加类型转换 }
虽然这样写,在传入参数错误的情况下也会抛出异常,但是这是运行时的错误,就跟程序本身没关系了
所以IL代码也需要加一个转换的操作
public static Action<object> ILTest() { var dm = new DynamicMethod("", null, new[] { typeof(object) }, typeof(MyClass)); var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Unbox_Any, typeof(int));//加上这一句拆箱操作 il.Emit(OpCodes.Call, typeof(MyClass).GetProperty("Name").GetSetMethod()); il.Emit(OpCodes.Ret); return (Action<object>)dm.CreateDelegate(typeof(Action<object>)); }
再来看运行结果
好了,这回就对了
- 重点:
别看我们在C#代码中类型转换操作都是一样的(Type)Object,但是在IL中值类型和引用类型会被编译成不同的指令,原因就是大家都知道的,值类型和引用类型的储存方式和位置不同引起的
object转值类型被称为拆箱,对应指令是OpCodes.Unbox_Any和OpCodes.Unbox(我不知道区别,一般都是用OpCodes.Unbox_Any)
object转引用类型就是强转,对应指令是OpCodes.Castclass
上面2个指令都需要提供一个指令参数,如il.Emit(OpCodes.Castclass, typeof(int));表示拆箱后的类型,或强转后的类型
值类型转为object称为装箱,对应指令是OpCodes.Box,不需要提供指令参数
引用类型转object,也是强转,对应指令是OpCodes.Castclass
所以其实我们昨天的栗子中有一部分也是需要改正的,在获取静态属性和字段值的时候,返回值的object,但是IL中没有进行转换
修改后就应该变成这样:
public static Func<object> ILTest() { var dm = new DynamicMethod("", typeof(object), null, typeof(MyClass)); var il = dm.GetILGenerator(); var MyClass_Name = typeof(MyClass).GetProperty("Name"); il.Emit(OpCodes.Call, MyClass_Name.GetGetMethod()); if (MyClass_Name.PropertyType.IsValueType)//判断属性类型是否是值类型 { il.Emit(OpCodes.Box);//如果是值类型就装箱 } else { il.Emit(OpCodes.Castclass, typeof(object));//引用类型就强转为object } il.Emit(OpCodes.Ret); return (Func<object>)dm.CreateDelegate(typeof(Func<object>)); }
- 转型的注意事项
在转型虽然不是必须的,但最好是带上,不然你不知道什么时候会出现一些莫名其妙的错误
比如下面这个栗子:
当把强转加上之后
虽然会导致程序异常,但至少比刚才那种情况好多了不是吗
- 下篇预告
实例属性/字段的读取与设置,及其实用价值