C# StringBuilder和'+';操作人员
我在这里维护这段代码,它通常有如下模式:C# StringBuilder和'+';操作人员,c#,.net,compiler-optimization,C#,.net,Compiler Optimization,我在这里维护这段代码,它通常有如下模式: StringBuilder result = new StringBuilder(); result.Append("{=" + field.Name + "={"); 这样做似乎浪费了大量无用的对象构造,我想重写如下: result.Append("{=").Append(field.Name).Append("={"); 第一个版本对GC施加了更大的压力,这是正确的吗?或者,在C#编译器中,如果字符串与字符串文字的连接不会创建临时对象,那么是否
StringBuilder result = new StringBuilder();
result.Append("{=" + field.Name + "={");
这样做似乎浪费了大量无用的对象构造,我想重写如下:
result.Append("{=").Append(field.Name).Append("={");
第一个版本对GC施加了更大的压力,这是正确的吗?或者,在C#编译器中,如果字符串与字符串文字的连接不会创建临时对象,那么是否有一些优化?我同意所有的答案,但对我来说,你需要理解C#中的字符串,以及它们实际上被“隐藏”处理的方式 当连接5个或5个以上的字符串时,StringBuilder的使用会自动进行。这是因为编译器本质上转换:
string a = b + c + d + e + f;
进入
因此,创建数组会有一个隐含的开销
我建议阅读Eric Lippert的以下内容,他在C#中编写了字符串连接:
请注意,
StringBuilder
是可变的,与String
不同,它是专门为以这种方式构建字符串而设计的。尽管使用大字符串进行字符串连接很容易给GC带来明显的压力,但如果您使用的是StringBuilder
,这通常不是一个问题
如果内存消耗是一个问题(对于大字符串可能是这样),并且您知道最终结果的大致长度,那么可以使用构造函数建议开始大小,并随着构建器的增长最小化内存重新分配
还请注意,您可以使用AppendFormat
将变量插入常量字符串,例如:
result.Append("{=").Append(field.Name).Append("={");
变成:
result.AppendFormat("{{={0}={{", field.Name);
这会产生完全相同的结果,并且在很大程度上取决于偏好而不是性能
最后,如果
result
是整个字符串(而不是正在构建的更大字符串的一部分),只需使用string.Format(“{{{0}={{”,field.Name)
或string.Concat(“{=”,field.Name.={”)
而不是StringBuilder
"虽然StringBuilder类通常比String类提供更好的性能,但您不应在需要操作字符串时自动使用StringBuilder替换字符串。性能取决于字符串的大小、为新字符串分配的内存量、应用程序正在执行的系统以及e操作类型。您应该准备好测试应用程序,以确定StringBuilder是否真的提供了显著的性能改进。“我实际为此构建并运行了多个测试。要获得测试结果,请跳到底部。我使用了此基准测试方法:
public static string BenchmarkMethod(Action method, int iterations)
{
var watch = new Stopwatch();
var results = new List<TimeSpan>(iterations);
for (int iteration = 0; iteration < iterations; iteration++)
{
watch.Start();
method();
watch.Stop();
results.Add(watch.Elapsed);
watch.Reset();
}
var builder = new StringBuilder();
builder.Append("Method benchmarked: ");
builder.Append(method.Method.ReflectedType);
builder.Append(".");
builder.AppendLine(method.Method.Name);
builder.Append("Average time in ticks: ");
builder.AppendLine(results.Average(t => t.Ticks).ToString());
return builder.ToString();
}
以及:
其中strings
是一个包含9个30个字母字符串的字符串数组
它们的范围从1到8个concats/附录。我最初编写它们是从1到6,使用3个字母字符串,并采集了10000个样本
更新:我已经得到了更多的样本(准确地说是100万)在字符串中添加更多的字母。显然,使用StringBuilder完全是对性能的浪费。在使用StringBuilder的30个字母中,使用+
操作符所需的时间是使用+
操作符所需时间的两倍……在现在需要几秒钟才能完成的测试中,我想我应该退出这个主题
最后更新:这也是非常重要的。使用+
操作符和StringBuilder的区别在于当您在不同的行上进行连接时。这种方法实际上比使用StringBuilder花费的时间更长:
public static void StringConcatAltOperatorX8()
{
var foo = strings[0];
foo += strings[1];
foo += strings[2];
foo += strings[3];
foo += strings[4];
foo += strings[5];
foo += strings[6];
foo += strings[7];
foo += strings[8];
}
因此,在每个字符串30个字母和100万个样本的情况下,在同一个调用中将所有字符串组合成一个字符串大约需要5.809297个刻度。将所有字符串组合成单独的行大约需要12.933227个刻度。使用StringBuilder需要11.27558个刻度。我对回复的长度表示歉意。这是我需要在我的self.编译器足够聪明,可以生成对字符串之一的调用。Concat()重载。如果你只使用StringBuilder进行连接,那么你的情况实际上会更糟。这就是你使用StringBuilder的全部目的吗?如果是,这是浪费。只使用字符串连接。@AntP:我想OP忽略了循环,他只是想知道字符串连接和
StringBuilder
的唯一用例,当连接需要在多行或循环中发生时。否则,您只需使用二进制加法运算符。
public static void StringConcatOperatorX8()
{
var foo = strings[0] + strings[1] + strings[2] + strings[3] + strings[4] + strings[5] + strings[6] + strings[7] + strings[8];
}
public static void StringBuilderAppendsX8()
{
var builder = new StringBuilder();
builder.Append(strings[0]);
builder.Append(strings[1]);
builder.Append(strings[2]);
builder.Append(strings[3]);
builder.Append(strings[4]);
builder.Append(strings[5]);
builder.Append(strings[6]);
builder.Append(strings[7]);
builder.Append(strings[8]);
var result = builder.ToString();
}
public static void StringConcatAltOperatorX8()
{
var foo = strings[0];
foo += strings[1];
foo += strings[2];
foo += strings[3];
foo += strings[4];
foo += strings[5];
foo += strings[6];
foo += strings[7];
foo += strings[8];
}