为什么在值类型上隐式调用toString会导致box指令

这更像是一个“奇怪的原因”,而不是一个特定的问题,但请看下面的代码

static void Main(string[] args) { int val = 10; Console.WriteLine("val is {0}", val); // (1) Console.WriteLine("val is {0}", val.ToString()); //(2) } 

在情况(1)中输出以下IL

 IL_0000: nop IL_0001: ldc.i4.s 10 IL_0003: stloc.0 IL_0004: ldstr "val is {0}" IL_0009: ldloc.0 IL_000a: box [mscorlib]System.Int32 IL_000f: call void [mscorlib]System.Console::WriteLine(string, object) 

在我明确调用toString方法的情况下(2)我得到了

 IL_0014: nop IL_0015: ldstr "val is {0}" IL_001a: ldloca.s val IL_001c: call instance string [mscorlib]System.Int32::ToString() IL_0021: call void [mscorlib]System.Console::WriteLine(string, object) 

所以在case(1)中,即使int重写toString,值类型也会被装箱,并且调用toString方法,这可能会调用vtable覆盖

所以结果完全相同,但显式的toString避免了装箱操作

谁知道为什么?

= =编辑
可以清楚,令我感到困惑的是,我开始假设即使int派生自System.ValueType,而System.ValueType又派生自System.Object,因为它包含toString,GetHashCode等。
所以在我的天真视图中(可能来自C ++),如果我覆盖从System.Object派生的方法,那么就不需要强制转换为System.Object(并因此将值类型框),因为存在overriden方法并且编译器将自动引用该类型的vtable条目。
我也假设调用Console.WriteLine() 隐式调用int.toString所以也许这就是我出错的地方。 希望有道理

好的 – 全部排序。 谢谢所有人让我直截了当。 所有这些都与我的错误假设有关,即Console.WriteLine正在进行隐式字符串转换。 不要问我为什么这么想 – 看起来很明显现在错误:)

你根本没有隐含地调用ToStringWriteLine方法没有重载,它接受格式字符串后的字符串,它只接受对象。

因此,您不是隐式调用ToString ,而是隐式地将int转换为object 。 第一种情况相当于:

 Console.WriteLine("val is {0}", (object)val); 

由于int是值类型,因此发生装箱。

第二种情况相当于:

 Console.WriteLine("val is {0}", (object)val.ToString()); 

由于字符串是引用类型,将其强制转换为对象实际上不会导致发出任何代码。 它只是将类型与方法签名匹配。

因为在第一个实例中,您在调用Console.WriteLine()函数时将int作为object传递。 这会强制int被装箱。 在第二种方法中,你直接调用ToString ,这避免了装箱并将string传递给WriteLine ,后者已经是一个引用类型。

在第一次调用中根本没有.ToString调用。 而是调用函数Console.WriteLine(object)。 第一个参数是int类型,必须加框以满足类型对象。 稍后在WriteLite内部,将在对象上调用.ToString。