在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())
- 这是真的吗?我倾向于相信作者的观点证据。
- 如果这是真的,为什么编译器不直接这样做呢为你?也许自2006年以来有所改变?有人知道吗?(我没有时间/经验做整个IL分析)
编译器不会为您做这些,因为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定义
- Int32是一个值类型,因此:
- myVal不可能是空引用*和
- 不可能有更派生的重写- Int32.ToString()是有效密封的。
callvirt
有两个原因。首先,有时候你确实希望它是一个虚拟电话。第二个是(更有争议的)他们决定禁止对空引用的任何方法调用,callvirt
有一个内置的测试。
在这种情况下,这两个都不适用。不能有更多的派生类覆盖Int32.ToString(),并且myVal不能为空。因此,它可以在传递Int32
而不装箱的ToString()
方法中引入call
。
这种组合(值不能为空,方法不能在其他地方重写)只会更少地出现引用类型,因此编译器不能充分利用它(它也不会花费太多,因为它们不必被装箱)。
如果Int32
继承了一个方法实现,则不是这种情况。例如,myVal.GetType()
会装箱myVal
,因为没有Int32
覆盖——不可能有,它不是虚拟的——所以它只能通过将myVal
视为对象,通过装箱来访问。
这意味着c#编译器将对非虚拟方法使用callvirt
,有时对虚拟方法使用call
,这一事实不无讽刺意味。
为什么不将每种方法分别尝试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包。这对我来说完全有意义,如果你照顾垃圾。