String.Format(…)中的Boxing和Unboxing ……以下是合理化的吗?

我正在做一些关于装箱/拆箱的阅读,事实certificate,如果你做一个普​​通的String.Format() ,你在object[]参数列表中有一个值类型,它将导致一个装箱操作。 例如,如果您正在尝试打印出整数的值并执行string.Format("My value is {0}",myVal) ,它会将myVal int粘贴到一个框中并在其上运行ToString函数。

浏览, 我发现了这篇文章 。

看来你可以简单地通过在值类型上执行.ToString ,然后将其交给string.Format函数: string.Format("My value is {0}",myVal.ToString())来避免拳击惩罚

  1. 这是真的吗? 我倾向于相信作者的证据。
  2. 如果这是真的,为什么编译器不能简单地为你做这个? 也许它自2006年以来发生了变化? 有人知道吗? (我没有时间/经验来进行整个IL分析)

编译器不会为您执行此操作,因为string.Format采用params Object[] 。 拳击是因为转换为Object

我不认为编译器倾向于特殊情况方法,因此它不会在这种情况下删除装箱。

是的,在许多情况下,如果首先调用ToString() ,编译器将不会执行装箱。 如果它使用Object的实现,我认为它仍然必须打包。

最终, string.Format解析格式字符串本身将比任何装箱操作慢得多,因此开销可以忽略不计。

1:是的, 只要 value-type重写ToString() ,所有内置类型都会这样做。

2:因为规范中没有定义这样的行为,并且正确处理params object[] (wrt value-types)是:拳击

string.Format就像任何其他不透明方法一样; 这样做的事实对编译器来说是不透明的。 如果模式包含像{0:n2}这样的格式(这需要特定的转换,而不仅仅是ToString() ),那么它在function上也是不正确的。 试图理解模式是不可取的和不可靠的,因为模式可能直到运行时才知道。

通过使用StringBuilder或StringWriter构造字符串并使用类型化的重载来避免装箱会更好。

大多数时候拳击应该没什么问题,甚至不值得你去了解它。

首先是简单的。 编译器没有将string.Format("{0}", myVal)转换为string.Format{"{0}", myVal.ToString())的原因是它没有理由应该这样做。 它应该将BlahFooBlahBlah(myVal)变成BlahFooBlahBlah(myVal.ToString())吗? 也许这会产生相同的效果,但是为了获得更好的性能,但是它可能会引入一个bug。 编译错误! 没有cookies!

除非可以从一般原则中推断出某些东西,否则编译器应该单独留下。

现在有趣的一点IMO:为什么前者导致拳击而后者没有。

对于前者,由于唯一匹配的签名是string.Format(string, object) ,因此必须将整数转换为要传递给方法的对象(盒装),该方法期望接收字符串和对象。

另外一半,为什么myVal.ToString()框也没有?

当编译器遇到这段代码时,它具有以下知识:

  1. myVal是一个Int32。
  2. ToString()由Int32定义
  3. Int32是一个值类型,因此:
  4. myVal不可能是空引用*和:
  5. 不可能有更多派生的覆盖 – Int32.ToString()被有效密封。

现在,通常C#编译器使用callvirt进行所有方法调用有两个原因。 首先,有时候你确实希望它成为一个虚拟的电话。 第二个是(更有争议的)他们决定禁止对null引用的任何方法调用,并且callvirt有一个内置的测试。

但在这种情况下,这些都不适用。 不能有更多派生类重写Int32.ToString(),myVal不能为null。 因此,它可以引入对ToString()方法的call ,该方法在没有装箱的情况下传递Int32

这种组合(值不能为空,方法不能在别处覆盖)只能更少地引用引用类型,因此编译器无法获得它的那么多优点(它也不会花费那么多,因为他们不必被装箱)。

如果Int32inheritance了方法实现,则情况并非如此。 例如, myVal.GetType()会将myVal包装起来,因为没有Int32覆盖 – 不可能,它不是虚拟的 – 所以它只能通过将myVal视为对象来进行访问。

事实上,这意味着C#编译器将使用callvirt进行非虚方法,有时需要call虚方法,这一点并非毫无讽刺意味。

*请注意,即使是设置为null的可空整数也与此方面的空引用不同。

为什么不尝试每种方法一亿次左右,看看需要多长时间:

 static void Main(string[] args) { Stopwatch sw = new Stopwatch(); int myVal = 6; sw.Start(); for (int i = 0; i < 100000000; i++) { string string1 = string.Format("My value is {0}", myVal); } sw.Stop(); Console.WriteLine("Original method - {0} milliseconds", sw.ElapsedMilliseconds); sw.Reset(); sw.Start(); for (int i = 0; i < 100000000; i++) { string string2 = string.Format("My value is {0}", myVal.ToString()); } sw.Stop(); Console.WriteLine("ToStringed method - {0} milliseconds", sw.ElapsedMilliseconds); Console.ReadLine(); } 

在我的机器上,我发现.ToStringed版本在原始版本的大约95%的时间内运行,因此一些经validation据可以获得轻微的性能优势。

 string.Format("My value is {0}", myVal)
myVal is an object

string.Format("My value is {0}",myVal.ToString())
myVal.ToString() is a string

ToString重载,因此编译器无法为您决定。