我一直在我的C#项目中进行一些代码进行重构。我得到了重音代码分析警告:
"冗余字符串插值"
在以下情况下发生这种情况:
void someFunction(string param)
{
...
}
someFunction($"some string");
我已经阅读了字符串插值在编译时重写为string.Format
。然后我尝试了以下内容:
someFunction(string.Format("some string"));
这次我得到:
冗余字符串。format调用。
我的问题是:除了代码清洁外,是否受这些冗余呼叫影响的运行时性能或性能相同:
someFunction($"some string")
someFunction("some string")
someFunction(string.Format("some string"))
作为C#编译器中该特定优化的作者,我可以确认$"some string"
由C#编译器优化为"some string"
。这是一个常数,因此实际上无需在运行时执行代码来计算它。
另一方面,string.Format("some string")
是一种方法调用,必须在运行时调用该方法。显然,该呼叫的成本是相关的。当然,它不会做任何有用的事情,因此警告"冗余string.format调用。"
更新:实际上,编译器始终将无填充插入的插值优化为由此产生的字符串。它所做的就是将{{
拆除至{
和}}
至}
。我的更改是优化所有填充插入的插值,而无需格式化字符串串联。
好吧,让我们执行一个基准:
private static long someFunction(string value) {
return value.Length;
}
...
Stopwatch sw = new Stopwatch();
int n = 100_000_000;
long sum = 0;
sw.Start();
for (int i = 0; i < n; ++i) {
// sum += someFunction("some string");
// sum += someFunction($"some string");
sum += someFunction(string.Format("some string"));
}
sw.Stop();
Console.Write(sw.ElapsedMilliseconds);
结果(.NET 4.8 IA-64版本(,平均结果:
224 // "some string"
225 // $"some string"
8900 // string.Format("some string")
因此,我们可以看到,编译器删除了不需要的$
,但是执行string.Format
,这浪费了时间来理解我们没有任何格式
public void XYZ()
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000000; i++)
{
someFunction("some string");
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for (int i = 0; i < 1000000; i++)
{
someFunction("$some string");
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for (int i = 0; i < 1000000; i++)
{
someFunction(string.Format("some string"));
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
}
private void someFunction(string param)
{
}
给我
3
3
210
因此,如果您不必: - (
调用string.Format("some string")
做很多事情,即使格式字符串之后没有格式的参数。
由于没有单个参数的string.Format
过载,因此运行时将首先需要实例化一个空参数数组(new object[0]
(将其传递给该方法。然后,它将从池中获取内部StringBuilder
实例,然后开始解析格式字符串,寻找占位符。如果您的格式字符串中有一个占位符,但没有参数,则抛出了例外,因此该方法始终解析格式字符串。
最后,StringBulder
必须实例化新的string
并在返回池之前复制其内容。
基于 @dmitry的分析,带有"空" FormattableString
,编译器可能足够聪明,可以跳过整个过程并仅通过字符串,因为在编译时间中评估了字符串的内容,以将插值的文字转换为FormattableString
实例。
我听说字符串插值在编译时间重写为
string.Format
这取决于;如果您的函数接受了FormattableString
参数,则编译器将创建一个从FormattableString
派生的类的实例,该类别将包含构造字符串和一个参数数组。这对于诸如条件调试之类的东西可能很有用,因为如果不需要字符串,您不需要支付格式化的费用,即:
public void Log_A(string input)
{
if (Log.IsDebugEnabled)
Log.Debug(input);
}
public void Log_B(FormattableString input)
{
if (Log.IsDebugEnabled)
Log.Debug(input.ToString());
}
// string.Format is called before entering Log_A
Log_A($"Something happened with {x} and {y}");
// if Log.IsDebugEnabled is false, string.Format will not be called
Log_B($"Something happened with {x} and {y}");