String.Format(...) 中的装箱和拆箱...以下内容是否合理?

发布于 2024-12-21 04:20:34 字数 715 浏览 2 评论 0原文

我正在阅读一些有关装箱/拆箱的文章,结果发现,如果您执行一个普通的 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())

  1. 这真的是真的吗?我倾向于相信作者的观点 证据。
  2. 如果这是真的,为什么编译器不简单地这样做 为你?也许自 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())

  1. Is this really true? I'm inclined to believe the author's
    evidence.
  2. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

月亮是我掰弯的 2024-12-28 04:20:34

编译器不会为您执行此操作,因为 string.Format 采用 params Object[]。装箱是由于转换为对象而发生的。

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

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

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

The compiler doesn't do this for you because string.Format takes a params Object[]. The boxing happens because of the conversion to Object.

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 from Object 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.

没有心的人 2024-12-28 04:20:34

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: boxing

string.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 just ToString()). Trying to understand the pattern is undesirable and unreliable since the pattern may not be known until runtime.

森林很绿却致人迷途 2024-12-28 04:20:34

最好通过使用 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.

我的黑色迷你裙 2024-12-28 04:20:34

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

除非可以从一般原则推理出某些事情,否则编译器应该不理会。

现在有趣的一点是:为什么前者会引起拳击而后者不会。

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

但另一方面,为什么 myVal.ToString() 也不装箱呢?

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

  1. myVal 是 Int32。
  2. ToString() 由 Int32 定义
  3. Int32 是值类型,因此:
  4. myVal 不可能是空引用* 并且:
  5. 不可能有更多派生的覆盖 - Int32.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) into string.Format{"{0}", myVal.ToString()), is that there's no reason why it should. Should it turn BlahFooBlahBlah(myVal) into BlahFooBlahBlah(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:

  1. myVal is an Int32.
  2. ToString() is defined by Int32
  3. Int32 is a value-type and therefore:
  4. myVal cannot possibly be a null reference* and:
  5. There cannot possibly be a more derived override - Int32.ToString() is effectively sealed.

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, and callvirt 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 the ToString() method that passes the Int32 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 instance myVal.GetType() would box myVal as there is no Int32 override - there can't be, it's not virtual - so it can only be accessed by treating myVal as an object, by boxing it.

The fact that this means that the C# compiler will use callvirt for non-virtual methods and sometimes call 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.

淡淡绿茶香 2024-12-28 04:20:34

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

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%,因此一些经验证据表明轻微的性能优势。

Why not try each approach a hundred million times or so and see how long it takes:

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

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.

歌入人心 2024-12-28 04:20:34
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 已重载,因此编译器无法为您做出决定。

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 is overloaded and therefore the compiler cannot decide for you.

奈何桥上唱咆哮 2024-12-28 04:20:34

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

.NET 中的内置字符串格式化工具非常强大且相当强大。
可用。不幸的是,他们还执行了数量可笑的 GC
分配。大多数情况下这些都是短暂的,在桌面 GC 上它们
一般不明显。然而,在更受约束的系统上,它们
可能会很痛苦。此外,如果您想跟踪 GC 使用情况
通过程序中的实时报告,您可能很快就会注意到
尝试打印当前 GC 状态会导致额外的结果
分配,挫败了整个检测尝试。

因此这个库的存在。并不是完全分配
自由的;有一些一次性设置成本。虽然稳定状态
完全无需分配。您可以自由使用字符串格式
游戏主循环中的实用程序,不会造成稳定的流失
垃圾。

我很快检查了图书馆的界面。作者没有使用参数包,而是使用带有手动定义的通用参数的函数。如果你处理好垃圾,这对我来说是完全有意义的。

I've found a StringFormatter project on GitHub. Description sounds very promising:

The built-in string formatting facilities in .NET are robust and quite
usable. Unfortunately, they also perform a ridiculous number of GC
allocations. Mostly these are short lived, and on the desktop GC they
generally aren't noticeable. On more constrained systems however, they
can be painful. Additionally, if you're trying to track your GC usage
via live reporting in your program, you might quickly notice that
attempts to print out the current GC state cause additional
allocations, defeating the entire attempt at instrumentation.

Thus the existence of this library. It's not completely allocation
free; there are several one-time setup costs. The steady state though
is entirely allocation-free. You can freely use the string formatting
utilities in the main loop of a game without it causing a steady churn
of garbage.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文