字符串插值是否评估重复使用?
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 可防止跨模块内联出现问题。另一方面,接口、委托和虚拟调用可以防止内联。
一旦编译器用函数体替换了两个调用,它就会看到来自同一字段的两次加载,并且可能可以使用公共子表达式优化来实际只获取一次字段(只要它能证明没有从同一个线程进行干预写入......特别是它不必担心来自其他线程的写入,除非有一个干预锁)。
但这只是节省了一个额外的字段获取。在访问器调用代价高昂的情况下(远程过程调用或数据库查找),内联将是不可能的,并且您将支付两次评估费用。在这种情况下,自己缓存结果是值得的。
如果我有一个多次使用同一个占位符的格式字符串,例如:
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 可防止跨模块内联出现问题。另一方面,接口、委托和虚拟调用可以防止内联。
一旦编译器用函数体替换了两个调用,它就会看到来自同一字段的两次加载,并且可能可以使用公共子表达式优化来实际只获取一次字段(只要它能证明没有从同一个线程进行干预写入......特别是它不必担心来自其他线程的写入,除非有一个干预锁)。
但这只是节省了一个额外的字段获取。在访问器调用代价高昂的情况下(远程过程调用或数据库查找),内联将是不可能的,并且您将支付两次评估费用。在这种情况下,自己缓存结果是值得的。