字符串插值是否评估重复使用?

Does string interpolation evaluate duplicated usage?

如果我有一个多次使用同一个占位符的格式字符串,例如:

emailBody = $"Good morning {person.GetFullName()}, blah blah blah, {person.GetFullName()} would you like to play a game?";

person.GetFullName() 是否被计算了两次,或者编译器是否足够聪明,知道它们是相同的值,应该被计算一次?

is the compiler smart enough to know these are the same value [...]?

但它不一定是相同的值。该方法很可能会在后续调用中return 产生不同的结果,因此它不能 缓存结果。

是的,它会被评估两次。它无法知道它 相同的值。例如:

Random rng = new Random();
Console.WriteLine($"{rng.Next(5)}, {rng.Next(5)}, {rng.Next(5)}");

这与您将表达式用作方法参数完全相同 - 如果您调用:

Console.WriteLine("{0} {1}", person.GetFullName(), person.GetFullName());

...您希望 GetFullName 被调用两次,不是吗?

如果您只想评估一次,请事先执行:

var name = person.GetFullName();
emailBody = $"Good morning {name}, blah blah blah, {name} would you like to play a game?";

或者只使用普通的 string.Format 调用:

emailBody = string.Format(
    "Good morning {0}, blah blah blah, {0} would you like to play a game?",
    person.GetFullName());

编译器无法确保方法始终 return 具有相同的值。如果您需要这样的优化,您有两个选择:

使用一个变量:

string fullName = person.GetFullName();
emailBody = $"Good morning {fullName}, blah blah blah, {fullName} would you like to play a game?";

使用String.Format:

emailBody = String.Format("Good morning {0}, blah blah blah, " +
            "{0} would you like to play a game?", person.GetFullName();

大多数时候,编译器将能够对此进行优化。不幸的是,它不能的时候也是你最关心的时候。

在此上下文中的优化不是基于两次调用同一函数,而是基于内联。访问器通常是微不足道的(查找字段,或者最多在数字类型之间进行转换或在数字和字符串类型之间进行 format/parse 转换),因此很容易满足内联的条件。此外,JIT 可防止跨模块内联出现问题。另一方面,接口、委托和虚拟调用可以防止内联。

一旦编译器用函数体替换了两个调用,它就会看到来自同一字段的两次加载,并且可能可以使用公共子表达式优化来实际只获取一次字段(只要它能证明没有从同一个线程进行干预写入......特别是它不必担心来自其他线程的写入,除非有一个干预锁)。

但这只是节省了一个额外的字段获取。在访问器调用代价高昂的情况下(远程过程调用或数据库查找),内联将是不可能的,并且您将支付两次评估费用。在这种情况下,自己缓存结果是值得的。