在String.Format(..)中装箱和拆箱…以下是合理的吗?

本文关键字:Format String | 更新日期: 2023-09-27 18:17:30

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

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

在将值类型传递给字符串之前,您可以简单地通过对其执行.ToString操作来避免装箱处罚。格式功能:string.Format("My value is {0}",myVal.ToString())

    这是真的吗?我倾向于相信作者的观点证据。
  1. 如果这是真的,为什么编译器不直接这样做呢为你?也许自2006年以来有所改变?有人知道吗?(我没有时间/经验做整个IL分析)

在String.Format(..)中装箱和拆箱…以下是合理的吗?

编译器不会为您做这些,因为string.Format接受params Object[]。装箱是因为转换为Object而发生的。

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

是的,在很多情况下,如果你先调用ToString(),编译器确实不会进行装箱。如果它使用Object的实现,我认为它仍然需要框。

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

1:是的,只要值类型覆盖ToString(),所有内置类型都这样做。

2:因为规范中没有定义这样的行为,params object[] (wrt值类型)的正确处理是:装箱

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

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

大多数情况下,拳击应该是微不足道的,甚至不值得你注意。

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

除非有什么东西可以从一般原则推理出来,否则编译器应该不管。

在我看来,有趣的是:为什么前者会导致拳击,而后者不会。

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

另一个问题是,为什么myVal.ToString()不盒子呢?

当编译器看到这段代码时,它知道:

    myVal是Int32。ToString()由Int32定义
  1. Int32是一个值类型,因此:
  2. myVal不可能是空引用*和
  3. 不可能有更派生的重写- Int32.ToString()是有效密封的。
现在,一般c#编译器对所有方法调用使用callvirt有两个原因。首先,有时候你确实希望它是一个虚拟电话。第二个是(更有争议的)他们决定禁止对空引用的任何方法调用,callvirt有一个内置的测试。

在这种情况下,这两个都不适用。不能有更多的派生类覆盖Int32.ToString(),并且myVal不能为空。因此,它可以在传递Int32而不装箱的ToString()方法中引入call

这种组合(值不能为空,方法不能在其他地方重写)只会更少地出现引用类型,因此编译器不能充分利用它(它也不会花费太多,因为它们不必被装箱)。

如果Int32继承了一个方法实现,则不是这种情况。例如,myVal.GetType()会装箱myVal,因为没有Int32覆盖——不可能有,它不是虚拟的——所以它只能通过将myVal视为对象,通过装箱来访问。

这意味着c#编译器将对非虚拟方法使用callvirt,有时对虚拟方法使用call,这一事实不无讽刺意味。

*请注意,在这方面,即使可空整数设置为null也不等同于null引用。

为什么不将每种方法分别尝试1亿次左右,看看需要多长时间呢?

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();
}

在我的机器上,我发现。tostring版本的运行时间大约是原始版本的95%,所以有一些经验证据表明性能有轻微的提高。

string.Format("My value is {0}", myVal)<br>
myVal is an object<br><br>
string.Format("My value is {0}",myVal.ToString())<br>
myVal.ToString() is a string<br><br>

ToString是重载的,因此编译器不能为你做决定。

我在GitHub上找到了一个StringFormatter项目。描述听起来很有希望:

.NET中内置的字符串格式化功能非常健壮可用。不幸的是,它们还执行了大量的GC配置。大多数都是短期的,在桌面GC中通常不明显。然而,在更受约束的系统中,它们可能会很痛苦。此外,如果您试图跟踪GC使用情况通过程序中的实时报道,您可能很快就会注意到这一点尝试打印当前GC状态会导致额外的错误分配,挫败了整个检测的尝试。

表示这个库的存在。这不是完全的分配免费的;有几个一次性设置成本。但是稳态完全与分配无关。您可以自由地使用字符串格式在游戏的主循环中添加实用工具,但不会造成稳定的用户流失垃圾。

我很快检查了库的接口。作者使用带有手动定义的generic参数的函数,而不是params包。这对我来说完全有意义,如果你照顾垃圾。