为什么可以在未装箱的值类型(没有基类)上调用虚拟System.Object成员?

时间:2013-02-22 22:00:17

标签: command-line-interface cil value-type boxing virtual-method

考虑这个IL片段(由Microsoft的C#编译器生成):

.class public sequential ansi sealed beforefieldinit Foo
       extends [mscorlib]System.ValueType
{ … }

.method private hidebysig static void  Main(string[] args) cil managed
{
  .maxstack  1
  .locals init ([0] valuetype Foo foo)

  ldloca.s          foo                                                   // ?
  constrained. Foo                                                        // ?
  callvirt          instance string [mscorlib]System.Object::ToString()   // ?
  pop
  ret
}

我想确切地知道标记为// ?的三行中发生了什么:如何调用虚拟方法(System.Object的{​​{1}}) unboxed 值类型,(根据CLI规范的I.8.9.7节)根本没有基类型?

我目前的不完全理解是:

  • ToString会导致局部变量ldloca.s foo的瞬态指针(*)(其中包含类型foo的未装箱值),在这种情况下根据CLI规范的第I.12.3.2.1节,可以在需要托管指针(valuetype Foo)的地方使用。

  • &指针将充当方法调用的 this 指针。这似乎是合法的,因为它可以在此处充当托管指针(*)。 CLI标准在第I.8.9.7节中提到了这种可能性。

  • &前缀用于防止将constrained. Foo值绑定到valuetype Foo对象。

但主要问题仍然存在:为什么可以在未继承该虚方法的未装箱值上调用虚方法?

1 个答案:

答案 0 :(得分:3)

  

如何在未装箱的值类型上调用虚方法System.Object.ToString,(根据CLI规范的I.8.9.7节)根本没有基类型?

我对这个问题感到困惑。拥有或不拥有基本类型的内容与它有什么关系?

  

我想知道三行中到底发生了什么

密钥是constrained前缀。文档 - 分区III第2.1节 - 非常简单。在文档中,我们有一种接收器thisType,一种指向该类型ptr的托管指针,以及constrained.callvirt method。规则是:

  
      
  1. 如果thisType是引用类型,则ptr将被取消引用,并作为this指针传递给callvirt
  2. method   
  3. 如果thisType是值类型且thisType实现method,那么ptr将作为this指针未经修改地传递给callmethod
  4. 实施的thisType   
  5. 如果thisType是值类型而thisType没有实现方法   ptr被取消引用,装箱,并作为this指针传递到callvirt
  6. method   

在你的例子中,第(3)点适用。类型Foo是一个值类型,它不实现方法ToString,因此它被加框并且方法(由基类提供)被调用,并引用该框作为{{1 }}

假设我们有int.ToString。然后点(2)适用。类型为this,它是值类型,int实现了int的覆盖。因此,System.Object.ToString()的托管指针成为int调用的this。从而省略了不必要的拳击。 (如果ToString突变ToString,则变异将发生在作为接收者给出的变量上,而不是在盒装副本上。)

  

为什么可以在未继承该虚方法的未装箱值上调用虚方法?

目前的问题是该方法是否实施,正如我在上面引用的文档中所述。继承与它有什么关系?

你没有问的问题,但这是一个很好的答案:

  

我应该总是在我的值类型上实现ToString吗?

好吧,我不知道总是,但这样做肯定是个好主意,因为(1)int的默认实现是令人沮丧的,(2)通过在您的值类型上实现它,您可以设法在任何直接调用方法时消除拳击惩罚。

  

对于其他虚拟对象方法是否也一样?

是的。并且有充分的理由无论如何都要在值类型中进行自己的平等和散列。默认值类型相等有时可能是意外的。

  

我注意到GetType不是虚拟的。这有关系吗?

是;不是虚拟意味着它不能在值类型中被覆盖,这意味着在任何值类型上调用ToString总是将其打包。当然,如果您手头有未装箱的值类型,那么您不需要调用GetType ,因为您已经知道它在编译时的类型是什么!