字符串插值中可避免的装箱

Avoidable boxing in string interpolation

使用字符串插值使我的字符串格式看起来更清晰,但是如果我的数据是值类型,我必须添加 .ToString() 调用。

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

var person = new Person { Name = "Tom", Age = 10 };
var displayText = $"Name: {person.Name}, Age: {person.Age.ToString()}";

.ToString() 使格式变得更长更丑陋。我试图摆脱它,但 string.Format 是一个内置的静态方法,我无法注入它。你对此有什么想法吗?并且由于字符串插值是 string.Format 的语法糖,为什么他们在生成语法糖背后的代码时不添加 .ToString() 调用?我觉得可行。

我看不出如何避免编译器的装箱。 string.Format 的行为不是 C# 规范的一部分。您不能指望它会调用 Object.ToString()。事实上它没有:

using System;
public static class Test {
    public struct ValueType : IFormattable {
        public override string ToString() => "Object.ToString";
        public string ToString(string format, IFormatProvider formatProvider) => "IFormattable.ToString";
    }
    public static void Main() {
        ValueType vt = new ValueType();
        Console.WriteLine($"{vt}");
        Console.WriteLine($"{vt.ToString()}");
    }
}

随着最近发布的 C# 10 和 .NET 6,情况发生了变化。编译器已经过优化,可以更好地处理内插字符串。我在这里写的是我从 post by Stephen Toub.

本质上:早期版本的 .NET 将内插字符串 $"{major}.{minor}.{build}.{revision}"(变量全部为整数)翻译成这样的东西

var array = new object[4];
array[0] = major;
array[1] = minor;
array[2] = build;
array[3] = revision;
string.Format("{0}.{1}.{2}.{3}", array);

从 C#10 开始,编译器可以使用“内插字符串处理程序”。上面的字符串可以翻译成:

var handler = new DefaultInterpolatedStringHandler(literalLength: 3, formattedCount: 4);
handler.AppendFormatted(major);
handler.AppendLiteral(".");
handler.AppendFormatted(minor);
handler.AppendLiteral(".");
handler.AppendFormatted(build);
handler.AppendLiteral(".");
handler.AppendFormatted(revision);
return handler.ToStringAndClear();

根据作者的说法,这不仅消除了装箱的需要,还引入了进一步的增强功能。因此,新方法实现了“40% 的吞吐量改进和几乎 5 倍的内存分配减少”(Stephen Toub)。

而这只是冰山一角。除了内插字符串处理程序外,C#10 还引入了对内插字符串解释的进一步优化。

所以我不再过多担心性能,而是专注于清晰度和可读性。有可能我们花费大量时间试图以代码清晰度为代价来超越编译器,而没有取得任何实质性成果。我想说,除非我们有严重的性能问题,否则我们可以使用内插字符串而不走复杂的弯路。

限制。当我们翻译字符串时,情况可能会有所不同。那么我们的格式字符串就变成了一个动态资源,对吧?现在编译器无法在运行前解释字符串,我们又回到了 string.Format