• 从来就不可能精通:关于Boxing


    我的简历上从来就不敢出现“精通”两个字,但是每次有招聘简历推荐过来的时候,各种“精通”就映入了我的眼帘。无奈啊,确实还是不行啊。例如今天在逛园子的时候发现这篇文章的回复中出现了关于boxing的讨论。发现有一个情况我原来的理解是错误的。于是留此一篇备忘。

    一般来说当一个 value type 实例需要转换为一个 reference type 实例的时候需要进行装箱。例如:

    int number = 2;
    object o = number;
    o.ToString();

    如果查看其IL,可以发现 box 指令。在一般情况下,例如将value type实例 cast为一个reference type(或者接口形式)实例,或者函数参数的类型转换(转换为 reference type,或者cast 为 接口)。但是只有 box 指令才说明发生了装箱的操作吗?实际上还有其他的情况。

    首先,value type和其他的类型一样,都可以拥有自己的静态的或者实例的成员,包括方法(包括虚方法)和field。我们当然可以调用其虚方法,但是当我们必须通过虚函数表去查找虚函数的时候则必须boxing然后再去callvirt,如果仅仅是作为成员函数调用value type实例的虚函数实现的话则不需要boxing,直接用call指令就OK了。例如:

    int number = 3;
    number.ToString();

    其 IL 代码是:

    IL_0001:  ldc.i4.3    
    IL_0002:  stloc.0     
    IL_0003:  ldloca.s    00 
    IL_0005:  call        System.Int32.ToString

    这里直接调用 call 方法触发实例方法。并不用装箱。

    但是如果这样,令value type实例调用其没有实现的虚方法:

    var valueInstance = new ValueClass();
    valueInstance.ToString();

    就会产生如下的 IL 代码:

    IL_0001:  ldloca.s    00 
    IL_0003:  initobj     ValueClass
    IL_0009:  ldloca.s    00 
    IL_000B:  constrained. ValueClass
    IL_0011:  callvirt    System.Object.ToString

    其中没有 box 指令, 但是有 constrained. 指令。constrained. 指令和 callvirt 使用称为 constrained virtual call,这是在 CLR 2.0引入的。主要目的是为了处理泛型类型的实例化或者方法调用,不管泛型类型实际参数是value type 还是 reference type。但是constrained. 指令后面跟的类型参数并不一定非得是泛型参数,可以直接是具体类型。这种调用的规则如下:

    • 如果是一个reference type(此时这个实例的 this 指针是一个managed pointer,指向该reference type 实例),则this指针复引用返回的是reference type实例的引用。虚函数的调用就发生在reference type实例上。
    • 如果是一个value type(此时这个实例的this指针是一个managed pointer,指向该 value type实例),并且该value type实现了该函数,那么接下来的调用实际上是一个非虚调用,并直接作用在该 value type 实例上(这是因为 value type 都是 seal 的,其实现了的虚方法不可能再被其他的派生类使用)。
    • 如果是一个value type,并且该类型并没有实现基类的虚方法(其基类肯定是System.Object,System.ValueType或者System.Enum),则this指针的复引用会返回value type实例,并直接被boxing为object reference。virtcall会作用在object reference上。

    我们可以看到,最后一条就是我们前一段范例代码中的情况。

    当然,调用基类的非虚方法也需要boxing,例如:

    var valueInstance = new ValueClass();
    valueInstance.GetType();

    其首先会装箱, 然后直接用 call 调用基类的非虚方法。

    IL_0001:  ldloca.s    00 
    IL_0003:  initobj     ValueClass
    IL_0009:  ldloc.0     
    IL_000A:  box         ValueClass
    IL_000F:  call        System.Object.GetType

    总结一下,在以下的情况下可能发生装箱:

    1. 值类型和引用类型(接口)的类型转换,参数传递;
    2. 调用值类型实例未实现的基类的虚方法;
    3. 调用值类型父类的非虚方法。

    还有其它情况吗?欢迎补充。

  • 相关阅读:
    NIO(二)
    前端面试题整理及答案
    win10休眠后屏幕唤不醒
    Python 将多个列表相同索引的元素进行拼接并输出
    Airtest 常用方法
    windows 各种巨强工具包
    mysql 主从数据同步配置
    windows 10 21H1 顶部任务栏点击音量或其他图标不出弹框
    html5 tts(文字朗读)
    为 map 中不存在的 key 提供缺省值
  • 原文地址:https://www.cnblogs.com/lxconan/p/detail_on_boxing.html
Copyright © 2020-2023  润新知