String.Format(...) 中的装箱和拆箱...以下内容是否合理?
我正在阅读一些有关装箱/拆箱的文章,结果发现,如果您执行一个普通的 String.Format()
,其中 object[]
列表中有一个值类型code> 参数,它会引起装箱操作。例如,如果您尝试打印出整数的值并执行 string.Format("My value is {0}",myVal)
,它将保留您的 myVal< /code>
int
放入一个框中并对其运行 ToString
函数。
浏览周围,我找到了这篇文章。
看来您只需在将值类型传递给 string.Format 函数之前对值类型执行 .ToString
即可避免装箱处罚: string.Format("My value is {0} ",myVal.ToString())
- 这真的是真的吗?我倾向于相信作者的观点 证据。
- 如果这是真的,为什么编译器不简单地这样做 为你?也许自 2006 年以来有所改变?有人知道吗? (我没有时间/经验来进行整个 IL 分析)
I was doing some reading regarding boxing/unboxing, and it turns out that if you do an ordinary String.Format()
where you have a value type in your list of object[]
arguments, it will cause a boxing operation. For instance, if you're trying to print out the value of an integer and do string.Format("My value is {0}",myVal)
, it will stick your myVal
int
in a box and run the ToString
function on it.
Browsing around, I found this article.
It appears you can avoid the boxing penalty simply by doing the .ToString
on the value type before handing it on to the string.Format function: string.Format("My value is {0}",myVal.ToString())
- Is this really true? I'm inclined to believe the author's
evidence. - If this is true, why doesn't the compiler simply do this
for you? Maybe it's changed since 2006? Does anybody know? (I don't have the time/experience to do the whole IL analysis)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
编译器不会为您执行此操作,因为
string.Format
采用params Object[]
。装箱是由于转换为对象
而发生的。我不认为编译器倾向于特殊情况的方法,因此在这种情况下它不会删除装箱。
是的,在许多情况下,如果您首先调用
ToString()
,编译器确实不会进行装箱。如果它使用Object
的实现,我认为它仍然需要装箱。最终,格式字符串本身的 string.Format 解析将比任何装箱操作慢得多,因此开销可以忽略不计。
The compiler doesn't do this for you because
string.Format
takes aparams Object[]
. The boxing happens because of the conversion toObject
.I don't think the compiler tends to special case methods, so it won't remove boxing in cases like this.
Yes in many cases it is true that the compiler won't do boxing if you call
ToString()
first. If it uses the implementation fromObject
I think it would still have to box.Ultimately the
string.Format
parsing of the format string itself is going to be much slower than any boxing operation, so the overhead is negligible.1:是的,只要值类型覆盖
ToString()
,所有内置类型都会这样做。2:因为规范中没有定义此类行为,并且
params object[]
(wrt 值类型)的正确处理是:装箱string.Format 就像任何其他不透明方法一样;事实上,它要做的事情对编译器来说是不透明的。如果模式包含像
{0:n2}
这样的格式(需要特定的转换,而不仅仅是ToString()
),那么它在功能上也会不正确。尝试理解该模式是不可取的且不可靠的,因为该模式可能要到运行时才知道。1: yes, as long as the value-type overrides
ToString()
, which all the inbuilt types do.2: because no such behaviour is defined in the spec, and the correct handling of a
params object[]
(wrt value-types) is: boxingstring.Format is just like any other opaque method; the fact that it is going to do that is opaque to the compiler. It would also be functionally incorrect if the pattern included a format like
{0:n2}
(which requires a specific transformation, not justToString()
). Trying to understand the pattern is undesirable and unreliable since the pattern may not be known until runtime.最好通过使用 StringBuilder< 构造字符串来避免装箱/a> 或 StringWriter 并使用类型化重载。
大多数时候,拳击应该是无关紧要的,甚至不值得你去注意它。
It would be better to avoid the boxing by constructing the string with StringBuilder or StringWriter and using the typed overloads.
Most of the time the boxing should be of little concern and not worth you even being aware of it.
首先是简单的。编译器没有将
string.Format("{0}", myVal)
转换为string.Format{"{0}", myVal.ToString())
的原因code> 是没有理由这样做的。它应该将BlahFooBlahBlah(myVal)
转换为BlahFooBlahBlah(myVal.ToString())
吗?也许这会产生相同的效果,但性能会更好,但很可能会引入错误。编译器不好!没有饼干!除非可以从一般原则推理出某些事情,否则编译器应该不理会。
现在有趣的一点是:为什么前者会引起拳击而后者不会。
对于前者,由于唯一匹配的签名是 string.Format(string, object) ,因此必须将整数转换为对象(装箱)才能传递给期望接收字符串的方法和一个物体。
但另一方面,为什么
myVal.ToString()
也不装箱呢?当编译器遇到这段代码时,它具有以下知识:
现在,C# 编译器通常使用
callvirt
进行所有方法调用,原因有两个。首先,有时您确实希望它是一个虚拟呼叫。第二个是(更具争议性)他们决定禁止对空引用进行任何方法调用,而 callvirt 对此有一个内置测试。但在这种情况下,这些都不适用。不能有更派生的类重写 Int32.ToString(),并且 myVal 不能为 null。因此,它可以引入对
ToString()
方法的调用
,该方法无需装箱即可传递Int32
。这种组合(值不能为空,方法不能在其他地方重写)只会很少出现引用类型,因此编译器无法充分利用它(它也不会花费太多,因为它们不必装箱)。
如果
Int32
继承了方法实现,则情况并非如此。例如,myVal.GetType()
会将myVal
框起来,因为没有Int32
覆盖 - 不可能有,它不是虚拟的 - 所以它只能通过将myVal
视为对象并装箱来访问。事实上,这意味着 C# 编译器将使用
callvirt
来处理非虚拟方法,有时也会使用call
来处理虚拟方法,这并非没有一定程度的讽刺意义。*请注意,在这方面,即使可空整数设置为 null 也与 null 引用不同。
The easy one first. The reason that the compiler doesn't turn
string.Format("{0}", myVal)
intostring.Format{"{0}", myVal.ToString())
, is that there's no reason why it should. Should it turnBlahFooBlahBlah(myVal)
intoBlahFooBlahBlah(myVal.ToString())
? Maybe that'll have the same effect but for better performance, but chances are it'll introduce a bug. Bad compiler! No biscuit!Unless something can be reasoned about from general principles, the compiler should leave alone.
Now for the interesting bit IMO: Why does the former cause boxing and the latter not.
For the former, since the only matching signature is
string.Format(string, object)
the integer has to be turned into an object (boxed) to be passed to the method, which expects to receive a string and an object.The other half of this though, is why doesn't
myVal.ToString()
box too?When the compiler comes to this bit of code it has the following knowledge:
Now, generally the C# compiler uses
callvirt
for all method calls for two reasons. The first is that sometimes you do want it to be a virtual call after all. The second is that (more controversially) they decided to ban any method call on a null reference, andcallvirt
has a built-in test for that.In this case though, neither of those apply. There can't be a more derived class that overrides Int32.ToString(), and myVal cannot be null. It can therefore introduce a
call
to theToString()
method that passes theInt32
without boxing.This combination (value can't be null, method can't be overriden elsewhere) only comes up with reference types much less often, so the compiler can't take as much advantage of it then (it also wouldn't cost as much, since they wouldn't have to be boxed).
This isn't the case if
Int32
inherits a method implementaiton. For instancemyVal.GetType()
would boxmyVal
as there is noInt32
override - there can't be, it's not virtual - so it can only be accessed by treatingmyVal
as an object, by boxing it.The fact that this means that the C# compiler will use
callvirt
for non-virtual methods and sometimescall
for virtual methods, is not without a degree of irony.*Note that even a nullable integer set to null is not the same as a null reference in this regard.
为什么不尝试每种方法一亿次左右,看看需要多长时间:
在我的机器上,我发现 .ToStringed 版本的运行时间约为原始版本的 95%,因此一些经验证据表明轻微的性能优势。
Why not try each approach a hundred million times or so and see how long it takes:
On my machine I'm finding that the .ToStringed version is running in about 95% of the time that the original version takes, so some empirical evidence for a slight performance benefit.
ToString 已重载,因此编译器无法为您做出决定。
ToString is overloaded and therefore the compiler cannot decide for you.
我在 GitHub 上找到了一个 StringFormatter 项目。描述听起来很有希望:
我很快检查了图书馆的界面。作者没有使用参数包,而是使用带有手动定义的通用参数的函数。如果你处理好垃圾,这对我来说是完全有意义的。
I've found a StringFormatter project on GitHub. Description sounds very promising:
I've quickly checked the interface of library. Instead of params pack, author uses functions with manually defined
generic
arguments. Which completely makes sense for me, if you take care of garbage.