我开始使用StringBuilder
而不是直接连接,但似乎它缺少一个关键的方法。所以,我自己实现了它,作为扩展:
public void Append(this StringBuilder stringBuilder, params string[] args)
{
foreach (string arg in args)
stringBuilder.Append(arg);
}
这会导致以下混乱:
StringBuilder sb = new StringBuilder();
...
sb.Append(SettingNode);
sb.Append(KeyAttribute);
sb.Append(setting.Name);
进入这个:
sb.Append(SettingNode, KeyAttribute, setting.Name);
我可以使用sb.AppendFormat("{0}{1}{2}",...
,但这似乎更不受欢迎,但仍然更难阅读。我的扩展是一个好方法,还是以某种方式破坏了StringBuilder
的好处?我不是试图过早地优化任何东西,因为我的方法更多的是关于可读性而不是速度,但我也想知道我不是自己在脚下拍摄。
答案 0 :(得分:69)
我认为你的扩展没有问题。如果它适合你,那一切都很好。
我自己更喜欢:
sb.Append(SettingNode)
.Append(KeyAttribute)
.Append(setting.Name);
答案 1 :(得分:32)
这样的问题总是可以用简单的测试用例来解答。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace SBTest
{
class Program
{
private const int ITERATIONS = 1000000;
private static void Main(string[] args)
{
Test1();
Test2();
Test3();
}
private static void Test1()
{
var sw = Stopwatch.StartNew();
var sb = new StringBuilder();
for (var i = 0; i < ITERATIONS; i++)
{
sb.Append("TEST" + i.ToString("00000"),
"TEST" + (i + 1).ToString("00000"),
"TEST" + (i + 2).ToString("00000"));
}
sw.Stop();
Console.WriteLine("Testing Append() extension method...");
Console.WriteLine("--------------------------------------------");
Console.WriteLine("Test 1 iterations: {0:n0}", ITERATIONS);
Console.WriteLine("Test 1 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
Console.WriteLine("Test 1 output length: {0:n0}", sb.Length);
Console.WriteLine("");
}
private static void Test2()
{
var sw = Stopwatch.StartNew();
var sb = new StringBuilder();
for (var i = 0; i < ITERATIONS; i++)
{
sb.Append("TEST" + i.ToString("00000"));
sb.Append("TEST" + (i+1).ToString("00000"));
sb.Append("TEST" + (i+2).ToString("00000"));
}
sw.Stop();
Console.WriteLine("Testing multiple calls to Append() built-in method...");
Console.WriteLine("--------------------------------------------");
Console.WriteLine("Test 2 iterations: {0:n0}", ITERATIONS);
Console.WriteLine("Test 2 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
Console.WriteLine("Test 2 output length: {0:n0}", sb.Length);
Console.WriteLine("");
}
private static void Test3()
{
var sw = Stopwatch.StartNew();
var sb = new StringBuilder();
for (var i = 0; i < ITERATIONS; i++)
{
sb.AppendFormat("{0}{1}{2}",
"TEST" + i.ToString("00000"),
"TEST" + (i + 1).ToString("00000"),
"TEST" + (i + 2).ToString("00000"));
}
sw.Stop();
Console.WriteLine("Testing AppendFormat() built-in method...");
Console.WriteLine("--------------------------------------------");
Console.WriteLine("Test 3 iterations: {0:n0}", ITERATIONS);
Console.WriteLine("Test 3 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
Console.WriteLine("Test 3 output length: {0:n0}", sb.Length);
Console.WriteLine("");
}
}
public static class SBExtentions
{
public static void Append(this StringBuilder sb, params string[] args)
{
foreach (var arg in args)
sb.Append(arg);
}
}
}
在我的电脑上,输出为:
Testing Append() extension method...
--------------------------------------------
Test 1 iterations: 1,000,000
Test 1 milliseconds: 1,080
Test 1 output length: 29,700,006
Testing multiple calls to Append() built-in method...
--------------------------------------------
Test 2 iterations: 1,000,000
Test 2 milliseconds: 1,001
Test 2 output length: 29,700,006
Testing AppendFormat() built-in method...
--------------------------------------------
Test 3 iterations: 1,000,000
Test 3 milliseconds: 1,124
Test 3 output length: 29,700,006
因此,你的扩展方法只比Append()方法稍慢,并且比AppendFormat()方法略快,但在所有3种情况下,差异完全是无关紧要的。因此,如果您的扩展方法增强了代码的可读性,请使用它!
答案 2 :(得分:9)
创建额外阵列需要一点点开销,但我怀疑它是多少。你应该测量
如果结果表明创建字符串数组的开销很大,可以通过多次重载来缓解它 - 一个用于两个参数,一个用于三个,一个用于四个等等...所以只有当你获得更多数量的参数(例如六或七)时才需要创建数组。重载将是这样的:
public void Append(this builder, string item1, string item2)
{
builder.Append(item1);
builder.Append(item2);
}
public void Append(this builder, string item1, string item2, string item3)
{
builder.Append(item1);
builder.Append(item2);
builder.Append(item3);
}
public void Append(this builder, string item1, string item2,
string item3, string item4)
{
builder.Append(item1);
builder.Append(item2);
builder.Append(item3);
builder.Append(item4);
}
// etc
然后使用params
进行最后一次重载,例如
public void Append(this builder, string item1, string item2,
string item3, string item4, params string[] otherItems)
{
builder.Append(item1);
builder.Append(item2);
builder.Append(item3);
builder.Append(item4);
foreach (string item in otherItems)
{
builder.Append(item);
}
}
我当然希望这些(或者只是你原来的扩展方法)比使用AppendFormat
更快 - 毕竟需要解析格式字符串。
请注意,我没有让这些重载以伪递归方式相互调用 - 我怀疑它们会被内联,但如果它们不是设置新堆栈帧的开销等可能最终变得重要。 (我们假设如果我们到目前为止,数组的开销很大。)
答案 3 :(得分:3)
除了一点开销之外,我个人认为没有任何问题。绝对更具可读性。只要你通过合理数量的参数,我就不会发现问题。
答案 4 :(得分:2)
从清晰度的角度来看,您的扩展程序还可以。
如果你的项目从不超过5或6项,那么最好简单地使用.append(x).append(y).append(z)格式。
如果您处理数千个项目,StringBuilder本身只会为您带来性能提升。此外,每次调用方法时都会创建数组。
所以,如果你为了清晰起见,那就没关系。如果你是为了提高效率,那么你可能走错了路。
答案 5 :(得分:1)
我不会说你正在削弱它的效率,但是当一种更有效的方法可用时,你可能会做一些效率低下的事情。 AppendFormat就是我想你想要的。如果经常使用的{0} {1} {2}字符串太难看了,我倾向于将格式字符串放在上面的consts中,所以外观与扩展名大致相同。
sb.AppendFormat(SETTING_FORMAT, var1, var2, var3);
答案 6 :(得分:1)
我最近没有测试过,但在过去,StringBuilder实际上比普通字符串连接(“this”+“that”)慢,直到你得到大约7个连接。
如果这是循环中没有发生的字符串连接,您可能需要考虑是否应该使用StringBuilder。 (在循环中,我开始担心使用普通字符串连接进行分配,因为字符串是不可变的。)
答案 7 :(得分:1)
可能更快,因为对于许多追加,它最多只执行一次重新分配/复制步骤。
public void Append(this StringBuilder stringBuilder, params string[] args)
{
int required = stringBuilder.Length;
foreach (string arg in args)
required += arg.Length;
if (stringBuilder.Capacity < required)
stringBuilder.Capacity = required;
foreach (string arg in args)
stringBuilder.Append(arg);
}
答案 8 :(得分:0)
最终归结为哪一个导致较少的字符串创建。我有一种感觉,扩展将导致使用字符串格式更高的字符串数。但表现可能不会那么不同。
答案 9 :(得分:0)
克里斯,
受到this Jon Skeet response的启发(第二个回答),我稍微改写了你的代码。基本上,我添加了TestRunner方法,该方法运行传入函数并报告已用时间,从而消除了一些冗余代码。不要沾沾自喜,而应该作为自己的编程练习。我希望它有用。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace SBTest
{
class Program
{
private static void Main(string[] args)
{
// JIT everything
AppendTest(1);
AppendFormatTest(1);
int iterations = 1000000;
// Run Tests
TestRunner(AppendTest, iterations);
TestRunner(AppendFormatTest, iterations);
Console.ReadLine();
}
private static void TestRunner(Func<int, long> action, int iterations)
{
GC.Collect();
var sw = Stopwatch.StartNew();
long length = action(iterations);
sw.Stop();
Console.WriteLine("--------------------- {0} -----------------------", action.Method.Name);
Console.WriteLine("iterations: {0:n0}", iterations);
Console.WriteLine("milliseconds: {0:n0}", sw.ElapsedMilliseconds);
Console.WriteLine("output length: {0:n0}", length);
Console.WriteLine("");
}
private static long AppendTest(int iterations)
{
var sb = new StringBuilder();
for (var i = 0; i < iterations; i++)
{
sb.Append("TEST" + i.ToString("00000"),
"TEST" + (i + 1).ToString("00000"),
"TEST" + (i + 2).ToString("00000"));
}
return sb.Length;
}
private static long AppendFormatTest(int iterations)
{
var sb = new StringBuilder();
for (var i = 0; i < iterations; i++)
{
sb.AppendFormat("{0}{1}{2}",
"TEST" + i.ToString("00000"),
"TEST" + (i + 1).ToString("00000"),
"TEST" + (i + 2).ToString("00000"));
}
return sb.Length;
}
}
public static class SBExtentions
{
public static void Append(this StringBuilder sb, params string[] args)
{
foreach (var arg in args)
sb.Append(arg);
}
}
}
这是输出:
--------------------- AppendTest -----------------------
iterations: 1,000,000
milliseconds: 1,274
output length: 29,700,006
--------------------- AppendFormatTest -----------------------
iterations: 1,000,000
milliseconds: 1,381
output length: 29,700,006