字符串输出:C#中的格式或concat?

字符串输出:C#中的格式或concat?

String output: format or concat in C#?

假设您要输出或连接字符串。 您更喜欢以下哪种款式?

  • var p = new { FirstName ="Bill", LastName ="Gates" };

  • Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

  • Console.WriteLine(p.FirstName +"" + p.LastName);

你更喜欢使用格式还是简单地连接字符串? 什么是你最喜欢的? 其中一个伤害了你的眼睛吗?

你有任何理性的论据来使用一个而不是另一个吗?

我会去第二个。


令我惊讶的是,很多人立即想要找到执行速度最快的代码。如果一百万次迭代仍然需要不到一秒的时间来处理,那么最终用户是否会注意到这一点?不太可能。

Premature optimization = FAIL.

我选择String.Format选项,只是因为它从架构的角度来看最有意义。我不关心性能,直到它成为一个问题(如果确实如此,我会问自己:我是否需要一次连接一百万个名字?当然它们不会全部适合屏幕...)

考虑您的客户以后是否想要更改它以便他们可以配置是否显示"Firstname Lastname""Lastname, Firstname."使用Format选项,这很容易 - 只需换出格式字符串即可。使用concat,您将需要额外的代码。当然,在这个特定的例子中,这听起来并不是什么大不了的事情。


试试这个代码。

它是您的代码的略微修改版本。
1.我删除了Console.WriteLine,因为它可能比我想要测量的速度慢几个数量级。
2.我在循环之前启动秒表并在之后立即停止它,这样我就不会失去精度,如果该函数需要例如26.4滴答来执行。
你通过一些迭代划分结果的方式是错误的。看看如果你有1000毫秒和100毫秒会发生什么。在这两种情况下,将它除以1000000后将获得0 ms。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Stopwatch s = new Stopwatch();

var p = new { FirstName ="Bill", LastName ="Gates" };

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (p.FirstName +"" + p.LastName);
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();
s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", p.FirstName, p.LastName);
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();


Console.Clear();
Console.WriteLine(n.ToString()+" x result = string.Format("{0} {1}", p.FirstName, p.LastName); took:" + (fElapsedMilliseconds) +"ms -" + (fElapsedTicks) +" ticks");
Console.WriteLine(n.ToString() +" x result = (p.FirstName + " " + p.LastName); took:" + (cElapsedMilliseconds) +"ms -" + (cElapsedTicks) +" ticks");
Thread.Sleep(4000);

这是我的结果:

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 618ms - 2213706 ticks
1000000 x result = (p.FirstName +"" + p.LastName); took: 166ms - 595610 ticks


哦亲爱的 - 在阅读其中一个回复后,我试图颠倒操作的顺序 - 所以首先执行连接,然后是String.Format ......

1
2
3
4
Bill Gates
Console.WriteLine(p.FirstName +"" + p.LastName); took: 8ms - 30488 ticks
Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 0ms - 182 ticks

因此,操作的顺序会产生巨大差异,或者说第一次操作总是慢得多。

以下是不止一次完成操作的运行结果。我曾尝试更改订单,但一旦第一个结果被忽略,事情通常遵循相同的规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Bill Gates
Console.WriteLine(FirstName +"" + LastName); took: 5ms - 20335 ticks
Bill Gates
Console.WriteLine(FirstName +"" + LastName); took: 0ms - 156 ticks
Bill Gates
Console.WriteLine(FirstName +"" + LastName); took: 0ms - 122 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 181 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 122 ticks
Bill Gates
String.Concat(FirstName,"", LastName); took: 0ms - 142 ticks
Bill Gates
String.Concat(FirstName,"", LastName); took: 0ms - 117 ticks

正如您所看到的,相同方法的后续运行(我将代码重构为3种方法)逐渐加快。最快的似乎是Console.WriteLine(String.Concat(...))方法,然后是正常连接,然后是格式化的操作。

启动时的初始延迟可能是Console Stream的初始化,因为在第一次操作之前放置Console.Writeline("Start!")会使所有时间重新开始。


字符串是不可变的,这意味着在代码中反复使用相同的小块内存。将相同的两个字符串添加到一起并一遍又一遍地创建相同的新字符串不会影响内存。 .Net非常聪明,只是为了使用相同的内存引用。因此,您的代码并没有真正测试两个concat方法之间的区别。

试试这个尺码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Stopwatch s = new Stopwatch();

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0, sbElapsedMilliseconds = 0, sbElapsedTicks = 0;

Random random = new Random(DateTime.Now.Millisecond);

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (random.Next().ToString() +"" + random.Next().ToString());
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();

s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", random.Next().ToString(), random.Next().ToString());
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();

StringBuilder sb = new StringBuilder();
s.Start();
for(var i = 0; i < n; i++){
    sb.Clear();
    sb.Append(random.Next().ToString());
    sb.Append("");
    sb.Append(random.Next().ToString());
    result = sb.ToString();
}
s.Stop();
sbElapsedMilliseconds = s.ElapsedMilliseconds;
sbElapsedTicks = s.ElapsedTicks;
s.Reset();

Console.WriteLine(n.ToString() +" x result = string.Format("{0} {1}", p.FirstName, p.LastName); took:" + (fElapsedMilliseconds) +"ms -" + (fElapsedTicks) +" ticks");
Console.WriteLine(n.ToString() +" x result = (p.FirstName + " " + p.LastName); took:" + (cElapsedMilliseconds) +"ms -" + (cElapsedTicks) +" ticks");
Console.WriteLine(n.ToString() +" x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(" "); sb.Append(random.Next().ToString()); result = sb.ToString(); took:" + (sbElapsedMilliseconds) +"ms -" + (sbElapsedTicks) +" ticks");
Console.WriteLine("****************");
Console.WriteLine("Press Enter to Quit");
Console.ReadLine();

样本输出:

1
2
3
1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 513ms - 1499816 ticks
1000000 x result = (p.FirstName +"" + p.LastName); took: 393ms - 1150148 ticks
1000000 x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(""); sb.Append(random.Next().ToString()); result = sb.ToString(); took: 405ms - 1185816 ticks


可怜的翻译

如果你知道你的申请将保留英文,那么很好,保存时钟滴答。但是,许多文化通常会在地址中看到姓氏名字。

所以使用string.Format(),特别是如果你要将你的应用程序转到任何英语不是第一语言的地方。


以下是超过100,000次迭代的结果:

1
2
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took (avg): 0ms - 689 ticks
Console.WriteLine(p.FirstName +"" + p.LastName); took (avg): 0ms - 683 ticks

以下是替补代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Stopwatch s = new Stopwatch();

var p = new { FirstName ="Bill", LastName ="Gates" };

//First print to remove the initial cost
Console.WriteLine(p.FirstName +"" + p.LastName);
Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

int n = 100000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

for (var i = 0; i < n; i++)
{
    s.Start();
    Console.WriteLine(p.FirstName +"" + p.LastName);
    s.Stop();
    cElapsedMilliseconds += s.ElapsedMilliseconds;
    cElapsedTicks += s.ElapsedTicks;
    s.Reset();
    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    fElapsedMilliseconds += s.ElapsedMilliseconds;
    fElapsedTicks += s.ElapsedTicks;
    s.Reset();
}

Console.Clear();

Console.WriteLine("Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took (avg):" + (fElapsedMilliseconds / n) +"ms -" + (fElapsedTicks / n) +" ticks");
Console.WriteLine("Console.WriteLine(p.FirstName + " " + p.LastName); took (avg):" + (cElapsedMilliseconds / n) +"ms -" + (cElapsedTicks / n) +" ticks");

所以,我不知道谁的答案标记为答案:)


在一个简单的场景中连接字符串很好 - 它比复杂的东西更复杂,甚至是LastName,FirstName。使用这种格式,您可以一目了然地看到,在读取代码时字符串的最终结构是什么,通过连接,几乎不可能立即识别最终结果(除非有一个非常简单的例子)。

从长远来看,这意味着当你回来改变你的字符串格式时,你要么能够弹出并对格式字符串进行一些调整,要么皱起眉头并开始四处移动各种属性访问器与文本混合,更容易引入问题。

如果您使用的是.NET 3.5,那么您可以使用像这样的扩展方法,并获得一个简单的流程,关闭袖口语法,如下所示:

1
string str ="{0} {1} is my friend. {3}, {2} is my boss.".FormatWith(prop1,prop2,prop3,prop4);

最后,随着应用程序的复杂性增加,您可能会决定在应用程序中保持字符串,以便将它们移动到资源文件中以进行本地化或简单地转换为静态帮助程序。如果你一直使用格式,这将更容易实现,你的代码可以很简单地重构使用类似的东西

1
string name = String.Format(ApplicationStrings.General.InformalUserNameFormat,this.FirstName,this.LastName);

对于非常简单的操作,我会使用连接,但是一旦超出2或3个元素格式变得更合适IMO。

偏好String.Format的另一个原因是.NET字符串是不可变的,这样做会创建更少的临时/中间副本。


虽然我完全理解了我的第一个答案的风格偏好和选择连接,部分是基于我自己的偏好,但我的部分决定是基于连接速度更快的想法。因此,出于好奇,我对它进行了测试,结果非常惊人,特别是对于如此小的字符串。

使用以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();

    var p = new { FirstName ="Bill", LastName ="Gates" };

    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    Console.WriteLine("Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took:" + s.ElapsedMilliseconds +"ms -" + s.ElapsedTicks +" ticks");

    s.Reset();
    s.Start();
    Console.WriteLine(p.FirstName +"" + p.LastName);
    s.Stop();

    Console.WriteLine("Console.WriteLine(p.FirstName + " " + p.LastName); took:" + s.ElapsedMilliseconds +"ms -" + s.ElapsedTicks +" ticks");

我得到了以下结果:

1
2
3
4
Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 2ms - 7280 ticks
Bill Gates
Console.WriteLine(p.FirstName +"" + p.LastName); took: 0ms - 67 ticks

使用格式化方法慢了100多倍!连接甚至没有注册为1ms,这也是我输出计时器滴答的原因。


从现在到2015年8月19日一周,这个问题将正好七(7)年。 现在有一种更好的方法。 在可维护性方面更好,因为我没有进行任何性能测试而只是连接字符串(但这些日子是否重要?差异几毫秒?)。 使用C#6.0实现它的新方法:

1
2
var p = new { FirstName ="Bill", LastName ="Gates" };
var fullname = $"{p.FirstName} {p.LastName}";

这个新功能更好,IMO,在我们的情况下实际上更好,因为我们有代码,我们建立的查询字符串的值取决于某些因素。 想象一下我们有6个参数的一个查询字符串。 因此,而不是做一个,例如:

1
2
var qs = string.Format("q1={0}&q2={1}&q3={2}&q4={3}&q5={4}&q6={5}",
    someVar, anotherVarWithLongName, var3, var4, var5, var6)

in可以像这样写,更容易阅读:

1
var qs=$"q1={someVar}&q2={anotherVarWithLongName}&q3={var3}&q4={var4}&q5={var5}&q6={var6}";

更好的测试是使用Perfmon和CLR内存计数器观察内存。我的理解是,你想要使用String.Format而不仅仅是连接字符串的全部原因是,因为字符串是不可变的,所以你不必要为垃圾收集器增加临时字符串的负担,这些字符串需要在下一次传递中回收。

StringBuilder和String.Format虽然可能更慢,但内存效率更高。

字符串连接有什么坏处?


从C#6.0开始,内插字符串可用于执行此操作,从而进一步简化了格式。

1
2
3
var name ="Bill";
var surname ="Gates";
MessageBox.Show($"Welcome to the show, {name} {surname}!");

An interpolated string expression looks like a template string that contains expressions. An interpolated string expression creates a string by replacing the contained expressions with the ToString represenations of the expressions’ results.

内插字符串与String.Format具有相似的性能,但由于值和表达式是在线插入的,因此可读性和语法更短。

请参阅这篇关于字符串插值的dotnetperls文章。

如果您正在寻找一种默认的格式化字符串的方法,这在可读性和性能方面是有意义的(除非微秒会在您的特定用例中产生影响)。


通常我更喜欢前者,特别是当字符串变长时,它可以更容易阅读。

另一个好处是我相信性能之一,因为后者在将最终字符串传递给Console.Write方法之前实际执行了2个字符串创建语句。 String.Format在我认为的封面下使用StringBuilder,因此避免了多个连接。

但是应该注意,如果传递给String.Format(以及其他类似方法,如Console.Write)的参数是值类型,那么它们将在传入之前被加框,这可以提供自己的性能命中。博客文章就在这里。


对于基本字符串连接,我通常使用第二种样式 - 更容易阅读和更简单。但是,如果我正在进行更复杂的字符串组合,我通常会选择String.Format。

String.Format节省了很多引号和加号......

1
2
3
Console.WriteLine("User {0} accessed {1} on {2}.", user.Name, fileName, timestamp);
vs
Console.WriteLine("User" + user.Name +" accessed" + fileName +" on" + timestamp +".");

只保留了一些字符,但我认为,在这个例子中,格式使它更清晰。


我使用String.Format,但我也会在资源文件中使用格式字符串,因此可以将其本地化为其他语言。使用简单的字符串concat不允许你这样做。显然,如果您不需要本地化该字符串,这不是一个考虑的理由。这实际上取决于字符串的用途。

如果它将显示给用户,我会使用String.Format所以我可以进行本地化,如果我需要 - 并且FxCop会拼写检查它,以防万一:)

如果它包含数字或任何其他非字符串的东西(例如日期),我会使用String.Format,因为它让我可以更好地控制格式。

如果它是用于构建像SQL这样的查询,我会使用Linq。

如果要在循环内连接字符串,我会使用StringBuilder来避免性能问题。

如果它是用于某些输出的用户不会看到,并且不会影响性能我会使用String.Format因为我习惯使用它无论如何我只是习惯了:)


  • 格式化是".NET"的方式。某些重构工具(Refactor!for one)甚至会建议重构concat风格的代码以使用格式化样式。
  • 格式化更容易针对编译器进行优化(尽管第二种可能会被重构为使用快速的'Concat'方法)。
  • 格式化通常更清晰(特别是"花式"格式化)。
  • 格式化意味着对所有变量隐式调用'.ToString',这有利于提高可读性。
  • 根据"Effective C#",.NET的"WriteLine"和"Format"实现搞砸了,它们会自动显示所有值类型(这很糟糕)。"有效的C#"建议明确执行'.ToString'调用,恕我直言是假的(见Jeff的帖子)
  • 目前,编译器不会检查格式化类型提示,从而导致运行时错误。但是,这可以在将来的版本中进行修改。

  • 我根据可读性选择。
    当变量周围有一些文本时,我更喜欢格式选项。在这个例子中:

    1
    2
    Console.WriteLine("User {0} accessed {1} on {2}.",
                       user.Name, fileName, timestamp);

    即使没有变量名,你也能理解这个含义,而concat却被引号和+符号弄得乱七八糟,让我的眼睛迷惑:

    1
    2
    Console.WriteLine("User" + user.Name +" accessed" + fileName +
                     " on" + timestamp +".");

    (我借用迈克的例子因为我喜欢它)

    如果格式字符串没有变量名称那么多,我必须使用concat:

    1
       Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

    format选项让我读取变量名称并将它们映射到相应的数字。 concat选项不需要。我仍然对引号和+符号感到困惑,但另一种选择更糟糕。红宝石?

    1
       Console.WriteLine(p.FirstName +"" + p.LastName);

    性能方面,我希望format选项比concat慢,因为format需要解析字符串。我不记得必须优化这种指令,但如果我这样做,我会查看string方法,如Concat()Join()

    格式的另一个优点是格式字符串可以放在配置文件中。非常方便的错误消息和UI文本。


    如果您正在处理需要易于阅读的内容(这是大多数代码),我会坚持使用运算符重载版本,除非:

    • 代码需要执行数百万次
    • 你做了大量的concats(超过4吨)
    • 该代码针对Compact Framework

    在至少两种情况下,我会使用StringBuilder。


    如果您打算本地化结果,那么String.Format是必不可少的,因为不同的自然语言甚至可能没有相同顺序的数据。


    好东西!

    刚刚添加

    1
    2
    3
    4
    5
    6
    7
            s.Start();
            for (var i = 0; i < n; i++)
                result = string.Concat(p.FirstName,"", p.LastName);
            s.Stop();
            ceElapsedMilliseconds = s.ElapsedMilliseconds;
            ceElapsedTicks = s.ElapsedTicks;
            s.Reset();

    它甚至更快(我猜两个例子中都调用string.Concat,但第一个需要某种翻译)。

    1
    2
    3
    1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 249ms - 3571621 ticks
    1000000 x result = (p.FirstName +"" + p.LastName); took: 65ms - 944948 ticks
    1000000 x result = string.Concat(p.FirstName,"", p.LastName); took: 54ms - 780524 ticks

    我也更喜欢第二种,但我现在没有理性的论据支持这种立场。


    我认为这在很大程度上取决于输出的复杂程度。我倾向于选择当时最适合的场景。

    根据工作选择合适的工具:D无论哪个看起来最干净!


    由于我不认为这里的答案涵盖了所有内容,我想在这里做一个小小的补充。

    Console.WriteLine(string format, params object[] pars)调用string.Format。 '+'表示字符串连接。我不认为这总是与风格有关;我倾向于根据我所处的背景混合这两种风格。

    简短的回答

    您面临的决定与字符串分配有关。我会尽量简单。

    说你有

    1
    string s = a +"foo" + b;

    如果执行此操作,它将评估如下:

    1
    2
    3
    4
    5
    string tmp1 = a;
    string tmp2 ="foo"
    string tmp3 = concat(tmp1, tmp2);
    string tmp4 = b;
    string s = concat(tmp3, tmp4);

    tmp这里不是一个真正的局部变量,但它是JIT的临时变量(它被推到了IL堆栈上)。如果在堆栈上推送一个字符串(例如IL中的ldstr表示文字),则在堆栈上放置对字符串指针的引用。

    当你调用concat时,这个引用就成了一个问题,因为没有任何字符串引用可用,包含两个字符串。这意味着.NET需要分配一个新的内存块,然后用两个字符串填充它。这是一个问题的原因是因为分配相对昂贵。

    这改变了问题:如何减少concat操作的数量?

    所以,粗略的答案是:string.Format为> 1个concats,'+'将适用于1个concat。如果您不关心进行微观性能优化,string.Format在一般情况下可以正常工作。

    关于文化的一个注记

    然后有一种叫做文化的东西......

    string.Format使您可以在格式中使用CultureInfo。一个简单的运算符'+'使用当前的文化。

    如果您正在编写文件格式和f.ex,这尤其是一个重要的评论。 double您要"添加"到字符串的值。在不同的计算机上,如果不使用带有显式CultureInfostring.Format,最终可能会得到不同的字符串。

    F.ex.考虑如果你改变'。'会发生什么。对于',',同时编写逗号分隔值文件...在荷兰语中,小数点分隔符是逗号,因此您的用户可能只会得到一个"有趣"的惊喜。

    更详细的回答

    如果您事先不知道字符串的确切大小,最好使用这样的策略来分配您使用的缓冲区。首先填充松弛空间,然后复制数据。

    增长意味着分配新的内存块并将旧数据复制到新缓冲区。然后可以释放旧的内存块。在这一点上你得到了底线:增长是一项昂贵的操作。

    最实际的方法是使用过度分配策略。最常见的策略是以2的幂来分配缓冲区。当然,你必须比这更聪明一点(因为如果你已经知道你需要128个字符,从1,2,4,8增长是没有意义的)但你得到了图片。该政策确保您不需要我上面描述的太多昂贵的操作。

    StringBuilder是一个基本上以2的幂分配底层缓冲区的类。 string.Format在引擎盖下使用StringBuilder

    这使得您的决定成为整体定位和追加(-multiple)(w / w.o。文化)或仅仅分配和追加之间的基本权衡。


    我实际上喜欢第一个,因为当有很多变量与文本混合时,我觉得它更容易阅读。另外,使用string.Format(),呃,格式时更容易处理引号。这是对字符串连接的体面分析。


    哦,只是为了完整性,以下是比正常连接更快的几个滴答:

    1
    Console.WriteLine(String.Concat(p.FirstName,"",p.LastName));

    就个人而言,第二个就是你正在使用的所有东西都按照它将被输出的直接顺序。而第一个你需要将{0}和{1}与正确的var匹配,这很容易搞砸。

    至少它没有C ++ sprintf那么糟糕,如果你得到变量类型错误,整个事情就会爆炸。

    此外,由于第二个都是内联的,它不需要对所有{0}的东西进行任何搜索和替换,后者应该更快......虽然我不


    第一个(格式)对我来说看起来更好。它更具可读性,您不会创建额外的临时字符串对象。


    根据MCSD准备材料,微软建议在处理非常少量的连接(可能是2到4个)时使用+运算符。我仍然不确定为什么,但这是需要考虑的事情。


    我很好奇StringBuilder在这些测试中的表现。结果如下......

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    class Program {
       static void Main(string[] args) {

          var p = new { FirstName ="Bill", LastName ="Gates" };

          var tests = new[] {
             new { Name ="Concat", Action = new Action(delegate() { string x = p.FirstName +"" + p.LastName; }) },
             new { Name ="Format", Action = new Action(delegate() { string x = string.Format("{0} {1}", p.FirstName, p.LastName); }) },
             new { Name ="StringBuilder", Action = new Action(delegate() {
                StringBuilder sb = new StringBuilder();
                sb.Append(p.FirstName);
                sb.Append("");
                sb.Append(p.LastName);
                string x = sb.ToString();
             }) }
          };

          var Watch = new Stopwatch();
          foreach (var t in tests) {
             for (int i = 0; i < 5; i++) {
                Watch.Reset();
                long Elapsed = ElapsedTicks(t.Action, Watch, 10000);
                Console.WriteLine(string.Format("{0}: {1} ticks", t.Name, Elapsed.ToString()));
             }
          }
       }

       public static long ElapsedTicks(Action ActionDelg, Stopwatch Watch, int Iterations) {
          Watch.Start();
          for (int i = 0; i < Iterations; i++) {
             ActionDelg();
          }
          Watch.Stop();
          return Watch.ElapsedTicks / Iterations;
       }
    }

    结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Concat: 406 ticks
    Concat: 356 ticks
    Concat: 411 ticks
    Concat: 299 ticks
    Concat: 266 ticks
    Format: 5269 ticks
    Format: 954 ticks
    Format: 1004 ticks
    Format: 984 ticks
    Format: 974 ticks
    StringBuilder: 629 ticks
    StringBuilder: 484 ticks
    StringBuilder: 482 ticks
    StringBuilder: 508 ticks
    StringBuilder: 504 ticks

    我总是走string.Format()路线。能够在像Nathan这样的变量中存储格式是一个很大的优势。在某些情况下,我可能会附加一个变量,但是一旦连接多于一个变量,我将重构以使用格式。


    实际上,我昨天进行了这些测试,但是已经很晚了,所以我没有提出我的回复。

    底线似乎是他们平均花费相同的时间。我做了超过100000次迭代的测试。

    我也会尝试使用StringBuilder,当我回到家时,我会发布代码和结果。


    推荐阅读