会调用谁的ToString()?

use*_*098 5 .net boxing

众所周知,intToString()覆盖ToString()基类型方法的方法Object.

对于以下代码,

int x = 100;
object y = (object)x;
Console.Write(y.ToString());
Run Code Online (Sandbox Code Playgroud)

(1)谁叫ToString()?int还是对象?为什么?
(2)我们如何查看/查看真相?通过任何调试/工具?

Mar*_*ell 3

由于该值已装箱,因此编译器知道的所有内容都是object,因此它是对 的常规虚拟调用object.ToString(),然后它将获取ToString()该结构的重写。所以:它是object.ToString()调用的,并且Int32.ToString()覆盖是被执行的

private static void Main()
{
    int x = 100;
    object y = (object)x;
    Console.Write(y.ToString());
}
Run Code Online (Sandbox Code Playgroud)

变成(我的评论):

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] int32 x,
        [1] object y)

    // int x = 100;
    L_0000: ldc.i4.s 100
    L_0002: stloc.0

    // object y = (object)x; 
    L_0003: ldloc.0 
    L_0004: box int32
    L_0009: stloc.1

    // Console.Write(y.ToString());
    L_000a: ldloc.1 
    L_000b: callvirt instance string [mscorlib]System.Object::ToString()
    L_0010: call void [mscorlib]System.Console::Write(string)
    L_0015: ret 
}
Run Code Online (Sandbox Code Playgroud)

重要的一行位于L_000b; 定期虚拟调用object.ToString().

更有趣的是非装箱值类型;如果已知值类型有 a ToString(),那么它可以发出静态调用:

private static void Main()
{
    int x = 100;
    Console.Write(x.ToString());
}

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] int32 x)
    L_0000: ldc.i4.s 100
    L_0002: stloc.0 
    L_0003: ldloca.s x
    L_0005: call instance string [mscorlib]System.Int32::ToString()
    L_000a: call void [mscorlib]System.Console::Write(string)
    L_000f: ret 
}
Run Code Online (Sandbox Code Playgroud)

请参阅 处的静态调用 ( call) L_0005然而,在大多数值类型的情况下,它将使用约束调用,如果它覆盖, JIT 将解释为静态调用,如果没有覆盖,则将解释为虚拟调用:

private static void Main()
{
    var x = new KeyValuePair<int, string>(123, "abc");
    Console.Write(x.ToString());
}
Run Code Online (Sandbox Code Playgroud)

变成:

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<int32, string> x)
    L_0000: ldloca.s x
    L_0002: ldc.i4.s 0x7b
    L_0004: ldstr "abc"
    L_0009: call instance void [mscorlib]System.Collections.Generic.KeyValuePair`2<int32, string>::.ctor(!0, !1)
    L_000e: ldloca.s x
    L_0010: constrained [mscorlib]System.Collections.Generic.KeyValuePair`2<int32, string>
    L_0016: callvirt instance string [mscorlib]System.Object::ToString()
    L_001b: call void [mscorlib]System.Console::Write(string)
    L_0020: ret 
}
Run Code Online (Sandbox Code Playgroud)

“constrained”/“callvirt”对 atL_0010L_0016 一起构成了此构造,因此它实际上可能不是虚拟调用。JIT/运行时可以在上面做其他的事情。此处对此进行了更多讨论

请注意,常规程序class始终为此使用虚拟调用,但场景除外return base.ToString();,该场景是对基本类型实现的静态调用。