6

我正在通过 C#(第 4 版)阅读 CLR 这本书,不是作为 C# 的新手,而是作为一个了解该语言的人,试图提高我对 CLR 底层功能的掌握。

无论如何,在本书中给出了一个示例(pg127-131),当讨论值类型的装箱/拆箱时,该示例以对 Console.WriteLine 的调用结束,其中值类型被连接到作为参数传递的字符串。

这本书解释了装箱和拆箱/复制操作会导致开销,我已经知道了,但它随后指出可以通过在传入的值类型上运行 .ToString() 来优化该示例。

我创建了一个示例程序并对其进行编译,然后使用 ILDASM 检查它生成的 IL。带有 ToString 的版本本质上是相同的,但是用对 ToString 的“调用”替换了“box”指令(这并不令人震惊)。

我在 100000 次运行的循环中对代码进行了基准测试,没有任何区别(它会波动哪个更快)。我意识到在进行基准测试(缓存等)时会出现其他因素,但是按照本书的解释方式,即使在幼稚的基准测试中,我也希望在避免“盒子”指令时看到显着的差异。

仅仅是调用函数并没有好多少吗?ToString 中是否正在进行装箱操作,使好处无效并且这本书是错误的?有人可以对此有所了解吗?

作为参考,以下是两个 ILDASM 读数:


.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       24 (0x18)
  .maxstack  2
  .locals init (int32 V_0)
  IL_0000:  ldc.i4.4
  IL_0001:  stloc.0
  IL_0002:  ldloc.0
  IL_0003:  box        [mscorlib]System.Int32
  IL_0008:  ldstr      "."
  IL_000d:  call       string [mscorlib]System.String::Concat(object,
                                                              object)
  IL_0012:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0017:  ret
} // end of method Program::Main

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       25 (0x19)
  .maxstack  2
  .locals init (int32 V_0)
  IL_0000:  ldc.i4.4
  IL_0001:  stloc.0
  IL_0002:  ldloca.s   V_0
  IL_0004:  call       instance string [mscorlib]System.Int32::ToString()
  IL_0009:  ldstr      "."
  IL_000e:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
  IL_0013:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0018:  ret
} // end of method Program::Main
4

2 回答 2

5

CLR 可能会内联调用string.Concat(object,object),导致与您的“优化”版本相同的代码。请注意,C# 编译器会将很多此类优化留给 CLR,因为它有更好的工具来执行这些优化。

除了几个空检查(将被优化)之外,它只是调用string.Concat(left.ToString(),right.ToString())将被简化为,string.Concat(left,right.ToString())因为 CLR 会看到它ToString()只是返回this

因此,两种情况下的执行代码很可能是相同的。

于 2015-03-11T19:42:30.007 回答
0

You're missing the fact, that String.Concat will call ToString on provided object argument internally:

public static String Concat(Object arg0, Object arg1) {
    Contract.Ensures(Contract.Result<String>() != null);
    Contract.EndContractBlock();

    if (arg0 == null)
    {
        arg0 = String.Empty;
    }

    if (arg1==null) {
        arg1 = String.Empty;
    }
    return Concat(arg0.ToString(), arg1.ToString());
}

So the call instruction will be there anyway, but it's hidden from you inside Concat method call.

Calling ToString makes different Concat overload be picked, and this one will not call ToString internally.

IL_000d:  call       string [mscorlib]System.String::Concat(object, object)

vs.

IL_000e:  call       string [mscorlib]System.String::Concat(string, string)
于 2015-03-11T19:41:13.490 回答