玩转动态编译 - 高级篇:二,IL设置静态属性,字段和类型转换

时间:2022-04-26 20:12:41
  • 静态属性赋值

先来看 Reflector反射出的IL源码(感谢Moen的提示),这次用 Release模式编译,去掉那些无用的辅助指令

public void AAA(string s)
{
MyClass.Name = s;
}
.method public hidebysig instance void AAA(string s) cil managed
{
.maxstack
//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 //方法结束
}

玩转动态编译 - 高级篇:二,IL设置静态属性,字段和类型转换

  • 小贴士:

每个操作系统都会从堆栈中获取指定数量的参数,比如上一篇中的静态字段/属性取值操作,这个操作不需要用到任何参数,比如执行一个方法,这个方法签名有几个参数,就需要提供几个参数,再比如执行一次比较,需要提供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>));
}

进行一些测试

static void Main(string[] args)
{
var act = ILTest();
MyClass.Name = "";
Console.WriteLine(MyClass.Name);
act("aaaaa");
Console.WriteLine(MyClass.Name);
}

测试方法

aaaaa
请按任意键继续. . .

测试结果

静态属性赋值

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();
} 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>));
}

注意加了下划线的几个地方

再来看运行结果

static void Main(string[] args)
{
var act = ILTest();
MyClass.Name = ;
Console.WriteLine(MyClass.Name);
act();
Console.WriteLine(MyClass.Name);
}

测试代码


请按任意键继续. . .

虽然程序没有抛出异常,但是结果确错了...

其实这个时候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>));
}

再来看运行结果

玩转动态编译 - 高级篇:二,IL设置静态属性,字段和类型转换

好了,这回就对了

  • 重点:

别看我们在C#代码中类型转换操作都是一样的(Type)Object,但是在IL中值类型和引用类型会被编译成不同的指令,原因就是大家都知道的,值类型和引用类型的储存方式和位置不同引起的

object转值类型被称为拆箱,对应指令是OpCodes.Unbox_AnyOpCodes.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()
{
//编译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>));
}

昨天的栗子

修改后就应该变成这样:

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>));
}
  • 转型的注意事项

在转型虽然不是必须的,但最好是带上,不然你不知道什么时候会出现一些莫名其妙的错误

比如下面这个栗子:

错误的栗子

玩转动态编译 - 高级篇:二,IL设置静态属性,字段和类型转换

当把强转加上之后

玩转动态编译 - 高级篇:二,IL设置静态属性,字段和类型转换

虽然会导致程序异常,但至少比刚才那种情况好多了不是吗

  • 下篇预告

实例属性/字段的读取与设置,及其实用价值

玩转动态编译 - 高级篇:二,IL设置静态属性,字段和类型转换的更多相关文章

  1. - 高级篇:二&comma;IL设置静态属性&comma;字段和类型转换

    - 高级篇:二,IL设置静态属性,字段和类型转换 静态属性赋值 先来看 Reflector反射出的IL源码(感谢Moen的提示),这次用 Release模式编译,去掉那些无用的辅助指令 public ...

  2. 玩转动态编译 - 高级篇:一&comma;IL访问静态属性和字段

    IL介绍 通用中间语言(Common Intermediate Language,简称CIL,发音为"sill"或"kill")是一种属于通用语言架构和.NET ...

  3. Newtonsoft&period;Json高级篇:TypeNameHandling设置

    原文:Newtonsoft.Json高级篇:TypeNameHandling设置 此示例使用TypeNameHandling 设置在序列化JSON和读取类型信息时包含类型信息,以便在反序列化JSON时 ...

  4. 一&comma;IL访问静态属性和字段

    一,IL访问静态属性和字段 IL介绍 通用中间语言(Common Intermediate Language,简称CIL,发音为"sill"或"kill")是一 ...

  5. iOS开发——高级篇——换肤、静态库

    一.换肤 1.思路1> 解决方案1,使用颜色作为图片素材的命名关键字 问题1:要保证每套图片的文件名 颜色+ 名称.png的格式比较麻烦 问题2:如果要将某一个图片应用到其他皮肤不方便2> ...

  6. Python之路&lpar;第二十三篇&rpar; 面向对象初级:静态属性、静态方法、类方法

    一.静态属性 静态属性相当于数据属性. 用@property语法糖装饰器将类的函数属性变成可以不用加括号直接的类似数据属性. 可以封装逻辑,让用户感觉是在调用一个普通的数据属性. 例子 class R ...

  7. JAVA高级篇&lpar;二、JVM内存模型、内存管理之第二篇&rpar;

    本文转自https://zhuanlan.zhihu.com/p/25713880. JVM的基础概念 JVM的中文名称叫Java虚拟机,它是由软件技术模拟出计算机运行的一个虚拟的计算机. JVM也充 ...

  8. JAVA高级篇&lpar;二、JVM内存模型、内存管理之第一篇&rpar;

    JVM内存结构如 Java堆(Heap),是Java虚拟机所管理的内存中最大的一块.Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建.此内存区域的唯一目的就是存放对象实例,几乎所有的对象实 ...

  9. iOS开发——高级篇——二维码的生产和读取

    一.二维码的生成 从iOS7开始集成了二维码的生成和读取功能此前被广泛使用的zbarsdk目前不支持64位处理器 生成二维码的步骤:导入CoreImage框架通过滤镜CIFilter生成二维码 二维码 ...

随机推荐

  1. 40GbE网络之后

    Ethernet Alliance 成员则有近90家公司,主要成员包括:网路晶片厂商Intel.Broadcom,网路设备商Cisco.Juniper.Brocade.Dell.QLogic,以及储存 ...

  2. HDU 2896

    传送门:HDU 2896 病毒侵袭 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others ...

  3. c&num;中使用servicestackredis操作redis

    下载地址: https://github.com/mythz/ServiceStack.Redis 添加dll引用: using ServiceStack.Common.Extensions;usin ...

  4. 利用dropbox备份vps数据

    在VPS的数据最好定时备份,免得服务器出了什么问题,数据就全丢了.我使用dropbox定时同步wordpress文件夹和数据库信息. 首先下载dropbox ? 1 wget -O dropbox.t ...

  5. 物联网socket通讯设备android

    http://cache.baiducontent.com/c?m=9d78d513d99c16ee19bec1291a17a7384215c634608090027ea48439e573284b50 ...

  6. 【Impala篇】---Hue从初始到安装应用

    一.前述 Cloudera公司推出,提供对HDFS.Hbase数据的高性能.低延迟的交互式SQL查询功能.基于Hive使用内存计算,兼顾数据仓库.具有实时.批处理.多并发等优点 是CDH平台首选的PB ...

  7. No bean named &&num;39&semi;xxxxx&&num;39&semi; is defined异常&comma;已解决,这个坑很难发现,你get了吗

    出现No bean named 'xxxxx' is defined异常 没有定义名为xxx的bean 如果你的代码写的都对,根本问题只有一个地方出错了,那就是你的 basePackage=的包名路径 ...

  8. excel2007VBA绘图2

    '--------------------------------------------------------Module1------------------------------------ ...

  9. 再谈获取网站图标Icon

    上一篇文章讨论了一下获取网站图标方法,是通过从根目录直接获取和html解析结合的方式来获取的,并给出了相应的代码示例.这一篇来讨论一个更现成的方法,这个方法是从360导航的页面发现的,在导航页面中点击 ...

  10. (1)java8初体验

    很多博客都拿Comparator,我也贴一下吧. java8以前的匿名内部类用来排序. //匿名内部类 @Test public void java8Test() { Person p1 = new ...