C#10新特性之内插字符串改进

当我们在 C# 中添加内插字符串时,我们总觉得在性能和表现力方面,使用该语法可以做更多事情。

01 内插字符串处理程序

今天,编译器将内插字符串转换为对 string.Format 的调用。这会导致很多分配——参数的装箱、参数数组的分配,当然还有结果字符串本身。此外,它在实际插值的含义上没有任何回旋余地。.

在 C# 10 中,我们添加了一个库模式,允许 API “接管”对内插字符串参数表达式的处理。例如,考虑 StringBuilder.Append:

var sb = new StringBuilder();
sb.Append($"Hello {args[0]}, how are you?");

到目前为止,这将使用新分配和计算的字符串调用 Append(string? value) 重载,将其附加到 StringBuilder 的一个块中。但是,Append 现在有一个新的重载 Append(refStringBuilder.AppendInterpolatedStringHandler handler),当使用内插字符串作为参数时,它优先于字符串重载。

通常,当您看到 SomethingInterpolatedStringHandler 形式的参数类型时,API 作者在幕后做了一些工作,以更恰当地处理插值字符串以满足其目的。在我们的 Append 示例中,字符串 “Hello”、args[0] 和“,how are you?” 将单独附加到 StringBuilder 中,这样效率更高且结果相同。

有时您只想在特定条件下完成构建字符串的工作。一个例子是 Debug.Assert:

Debug.Assert(condition, $"{SomethingExpensiveHappensHere()}");

在大多数情况下,条件为真,第二个参数未使用。但是,每次调用都会计算所有参数,从而不必要地减慢执行速度。Debug.Assert 现在有一个带有自定义插值字符串构建器的重载,它确保第二个参数甚至不被评估,除非条件为假。

最后,这是一个在给定调用中实际更改字符串插值行为的示例:String.Create() 允许您指定 IFormatProvider 用于格式化插值字符串参数本身的洞中的表达式:

String.Create(CultureInfo.InvariantCulture, $"The result is {result}");

02 常量内插字符串

如果内插字符串的所有洞都是常量字符串,那么生成的字符串现在也是常量。这使您可以在更多地方使用字符串插值语法,例如属性:

[Obsolete($"Call {nameof(Discard)} instead")]

请注意,必须用常量字符串填充洞。其他类型,如数字或日期值,不能使用,因为它们对文化敏感,并且不能在编译时计算。