C#字符串插值是如何编译的?

22
我知道插值表达式是语法糖,等价于string.Format(),但它在使用字符串格式化方法时是否有特殊的行为/识别能力呢?
如果我有一个方法:
void Print(string format, params object[] parameters)

并使用插值对其进行以下调用:

Print($"{foo} {bar}");

以下哪个调用行最类似于字符串插值的编译结果?

Print(string.Format("{0} {1}", new[] { foo, bar }));
Print("{0} {1}", new[] { foo, bar });

问题的原因:像NLog这样的日志框架通常会推迟字符串格式化,直到确定将实际写入日志消息时才执行。一般来说,我更喜欢字符串插值语法,但我需要知道它是否会带来额外的性能损失。


1
我知道一个非常简单的方法来找出答案。尝试两种方法并比较生成的IL代码。 - itsme86
你可以使用 LinqPad 更快地查看 IL 代码。或者使用任何反编译工具,如 JustDecomplie 或 Reflector。 - Cihan Yakar
你的方法基本上是不安全的。如果我使用一个包含大括号的单个字符串来调用它,会怎样? - SLaks
@SLaks 它的安全性与 String.Format 已经一样安全或不安全了?只要以类似的方式记录,我认为这里没有问题。 - James Thorpe
@JamesThorpe:String.Format有同样的问题,但希望人们不会那样做。而Print($"{foo} {bar}");是有问题的。 - SLaks
@SLaks:当然,在现实世界中,你会想要一组方法重载来解决这个问题。 - Merad
1个回答

32

它可以通过两种方式编译。

如果您在期望 string 的位置使用字符串插值表达式,则会将其编译为对 string.Format 的调用。

基本上是这样的:

string s = $"now is {DateTime.Now}";

被转换成了这个:

string s = string.Format("now is {0}", DateTime.Now);

试试Roslyn吧,你会看到它的见证。

这里没有什么神奇的东西。

但是,如果你在需要一个FormattableString(.NET 4.6中的新类型)的地方使用它,它将被编译成对FormattableStringFactory.Create的调用:

public void Test(FormattableString s)
{
}

Test($"now is {DateTime.Now}");

这里的调用被转换为:

Test(FormattableStringFactory.Create("now is {0}", DateTime.Now));

在 Try Roslyn 中自行查看代码

总之,回答你的最后一个问题:

这个调用:

Print($"{foo} {bar}");

将被翻译为:

Print(string.Format("{0} {1}", foo, bar));
Print在调用之前,通过string.Format进行格式化,这将产生格式化的成本。如果你能添加或找到一个重载Print,它接受FormattableString,那么你可以推迟string.Format的实际成本,直到确定是否需要记录日志。无法确定这是否在运行时产生可衡量的差异。FormattableStringToString 方法允许指定IFormatProvider,这意味着你也可以推迟本地化转换。试试Try Roslyn
public static void Print(FormattableString s)
{
    Console.WriteLine("norwegian: " + s.ToString(CultureInfo.GetCultureInfo("nb-NO")));
    Console.WriteLine("us: " + s.ToString(CultureInfo.GetCultureInfo("en-US")));
    Console.WriteLine("swedish: " + s.ToString(CultureInfo.GetCultureInfo("sv-SE")));
}

总的来说,为了回答OP关于性能的问题,这将导致一个新的类被构造(ConcreteFormattableString,来自工厂),但是除非日志框架调用ToString方法,否则参数将不会被评估,也就是最终会被记录下来。 - James Thorpe
如果日志记录方法接受FormattableString,而日志框架只接受string,则在调用站点之前,将通过string.Format进行格式化,然后日志框架会确定是否需要记录日志。 - Lasse V. Karlsen
是的,抱歉,我有点急躁了——我已经在脑海中想象着用 FormattableString 替换我的日志包装器中所有的 String 参数了 :) - James Thorpe
2
请注意,添加一个接受FormattableString的重载方法是没有帮助的,因为在重载决议期间,string具有更高的优先级。您将不得不使用一个单独的方法名称。 - svick
也许使用较新版本的编译器?https://sharplab.io/#v2:EYLgZgpghgLgrgJwgZwLQFEAeAHJzkCWA9gHYBqUCBUwANigDQwgG0A+AAgAwAEHAjAG4AsACgOAZj4AmHgGEeAbzE9VfKRwAsPALIAKAJRKVa0wDdKPKDwC8PAEQALCLVpF7gnhYQ9gtngAk9opQAL48AO5ECLQAJh4mqqFioUA-在转换到更新版本时确实使用字符串连接。 - Lasse V. Karlsen
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接