字符串输出:C# 中的格式或连接?
-
08-06-2019 - |
题
假设您想要输出或连接字符串。您更喜欢以下哪种风格?
var p = new { FirstName = "Bill", LastName = "Gates" };
Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
Console.WriteLine(p.FirstName + " " + p.LastName);
您更喜欢使用格式还是简单地连接字符串?什么是你最喜欢的?其中之一会伤害您的眼睛吗?
你有什么合理的论据来使用其中一种而不是另一种吗?
我会选择第二个。
解决方案
试试这个代码。
这是您的代码的稍微修改的版本。
1.我删除了 Console.WriteLine,因为它可能比我想要测量的慢几个数量级。
2.我在循环之前启动秒表并在循环之后立即停止它,这样,如果函数需要 26.4 个时钟周期来执行,我就不会失去精度。
3.您将结果除以一些迭代的方式是错误的。看看如果有 1000 毫秒和 100 毫秒会发生什么。在这两种情况下,除以 1000000 后都会得到 0 毫秒。
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 结果 = string.Format("{0} {1}", p.FirstName, p.LastName);拿:618 毫秒 - 2213706 个刻度
1000000 x 结果 = (p.FirstName + " " + p.LastName);拿:166 毫秒 - 595610 个刻度
其他提示
我很惊讶这么多人立即想要找到执行速度最快的代码。 如果一百万次迭代仍然需要不到一秒的时间来处理,这对最终用户来说是否会引起注意?不太可能。
过早的优化=失败。
我会选择 String.Format
选择,只是因为从架构的角度来看它最有意义。我不关心性能,直到它成为一个问题(如果确实如此,我会问自己:我需要一次连接一百万个名字吗?当然它们不会全部适合屏幕......)
考虑您的客户以后是否想要更改它,以便他们可以配置是否显示 "Firstname Lastname"
或者 "Lastname, Firstname."
使用“格式”选项,这很容易 - 只需交换格式字符串即可。使用 concat,您将需要额外的代码。当然,在这个特定的例子中,这听起来没什么大不了的,但可以推断一下。
哦,天哪 - 在阅读了其他回复之一后,我尝试反转操作的顺序 - 所以首先执行连接,然后执行 String.Format...
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
因此,操作的顺序会产生巨大的差异,或者更确切地说,第一个操作总是慢得多。
以下是多次完成操作的运行结果。我尝试过更改顺序,但一旦忽略第一个结果,事情通常遵循相同的规则:
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.Writeline("Start!") 会使所有时间恢复正常。
字符串是不可变的,这意味着同一小块内存会在代码中反复使用。将相同的两个字符串添加在一起并一遍又一遍地创建相同的新字符串不会影响内存。.Net 足够聪明,只需使用相同的内存引用即可。因此,您的代码并没有真正测试两种 concat 方法之间的差异。
试穿一下尺码:
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();
示例输出:
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 次迭代后的结果:
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took (avg): 0ms - 689 ticks
Console.WriteLine(p.FirstName + " " + p.LastName); took (avg): 0ms - 683 ticks
这是工作台代码:
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,则可以使用扩展方法 像这个 并获得一个简单流畅的即兴语法,如下所示:
string str = "{0} {1} is my friend. {3}, {2} is my boss.".FormatWith(prop1,prop2,prop3,prop4);
最后,随着您的应用程序变得越来越复杂,您可能会决定为了在应用程序中合理地维护字符串,您希望将它们移动到资源文件中进行本地化,或者简单地移动到静态帮助程序中。如果您始终使用格式,那么这将更容易实现,并且您的代码可以非常简单地重构为使用类似的东西
string name = String.Format(ApplicationStrings.General.InformalUserNameFormat,this.FirstName,this.LastName);
对于非常简单的操作,我会使用串联,但一旦超过 2 或 3 个元素,格式在我看来就变得更合适。
更喜欢 String.Format 的另一个原因是 .NET 字符串是不可变的,这样做会创建更少的临时/中间副本。
虽然我完全理解风格偏好,并选择串联作为我的第一个答案,部分原因是我自己的偏好,但我的决定部分是基于串联会更快的想法。因此,出于好奇,我对其进行了测试,结果令人震惊,尤其是对于这么小的字符串。
使用以下代码:
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");
我得到以下结果:
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,这就是为什么我也输出计时器滴答声。
对于基本的字符串连接,我通常使用第二种样式 - 更易于阅读且更简单。但是,如果我要进行更复杂的字符串组合,我通常会选择 String.Format。
String.Format 节省了大量的引号和加号...
Console.WriteLine("User {0} accessed {1} on {2}.", user.Name, fileName, timestamp);
vs
Console.WriteLine("User " + user.Name + " accessed " + fileName + " on " + timestamp + ".");
仅保存了几个字符,但我认为,在这个示例中,格式使其更加清晰。
更好的测试是使用 Perfmon 和 CLR 内存计数器来观察内存。我的理解是,您想要使用 String.Format 而不仅仅是连接字符串的全部原因是,由于字符串是不可变的,因此您不必要地给垃圾收集器增加了需要在下一次回收的临时字符串的负担。
StringBuilder 和 String.Format 虽然可能较慢,但内存效率更高。
一般来说,我更喜欢前者,因为特别是当字符串变长时,它会更容易阅读。
我相信另一个好处是性能,因为后者在将最终字符串传递给 Console.Write 方法之前实际上执行了 2 个字符串创建语句。我相信 String.Format 在幕后使用了 StringBuilder,因此可以避免多重连接。
但应该注意的是,如果传递给 String.Format(以及其他此类方法,如 Console.Write)的参数是值类型,那么它们将在传递之前进行装箱,这可以提供自己的性能提升。 关于此的博客文章在这里.
从现在开始的一周(2015 年 8 月 19 日),这个问题将正好七 (7) 岁。现在有一种更好的方法可以做到这一点。 更好的 在可维护性方面,因为与仅连接字符串相比,我没有进行任何性能测试(但这现在重要吗?几毫秒的差异?)。新的做法是 C#6.0:
var p = new { FirstName = "Bill", LastName = "Gates" };
var fullname = $"{p.FirstName} {p.LastName}";
这个新功能是 更好的, ,国际海事组织,和 在我们的例子中实际上更好 因为我们有代码构建查询字符串,其值取决于某些因素。想象一个查询字符串,其中有 6 个参数。因此,不要这样做,例如:
var qs = string.Format("q1={0}&q2={1}&q3={2}&q4={3}&q5={4}&q6={5}",
someVar, anotherVarWithLongName, var3, var4, var5, var6)
in 可以这样写,更容易阅读:
var qs=$"q1={someVar}&q2={anotherVarWithLongName}&q3={var3}&q4={var4}&q5={var5}&q6={var6}";
从 C# 6.0 开始 内插字符串 可以用来做到这一点,这进一步简化了格式。
var name = "Bill";
var surname = "Gates";
MessageBox.Show($"Welcome to the show, {name} {surname}!");
内插字符串表达式看起来像包含表达式的模板字符串。内插字符串表达式通过将包含的表达式替换为表达式结果的 ToString 表示形式来创建字符串。
内插字符串与 String.Format 具有相似的性能,但由于值和表达式是内联插入的,因此提高了可读性并缩短了语法。
另请参阅 这篇 dotnetperls 文章 关于字符串插值。
如果您正在寻找一种默认方式来格式化字符串,那么这在可读性和性能方面是有意义的(除非微秒会在您的特定用例中产生影响)。
- 格式化是“.NET”的实现方式。某些重构工具(Refactor!其一)甚至会建议重构 concat 样式代码以使用格式化样式。
- 格式化更容易为编译器优化(尽管第二个可能会被重构以使用快速的“Concat”方法)。
- 格式通常更易于阅读(尤其是“花式”格式)。
- 格式化意味着对所有变量隐式调用“.ToString”,这有利于可读性。
- 根据“Effective C#”,.NET 的“WriteLine”和“Format”实现很混乱,它们自动装箱所有值类型(这很糟糕)。“Effective C#”建议显式执行“.ToString”调用,恕我直言,这是伪造的(请参阅 杰夫的发帖)
- 目前,编译器不会检查格式化类型提示,从而导致运行时错误。但是,这可能会在未来版本中进行修改。
我会使用 String.Format,但我也会在资源文件中包含格式字符串,以便可以将其本地化为其他语言。使用简单的字符串连接不允许您这样做。显然,如果您不需要本地化该字符串,那么这不是需要考虑的理由。这实际上取决于字符串的用途。
如果要向用户显示,我会使用 String.Format,这样我就可以在需要时进行本地化 - 并且 福克斯警察 会帮我检查拼写,以防万一:)
如果它包含数字或任何其他非字符串内容(例如日期),我会使用 String.Format 因为它给了我更多 控制格式.
如果是为了构建像 SQL 这样的查询,我会使用 林克.
如果要在循环内连接字符串,我会使用 字符串生成器 以避免性能问题。
如果它是用户看不到的某些输出,并且不会影响性能,我会使用 String.Format 因为无论如何我都有使用它的习惯,而且我只是习惯了:)
如果您正在处理需要易于阅读的内容(这是大多数代码),我会坚持使用运算符重载版本,除非:
- 代码需要执行数百万次
- 你正在进行大量的连接(超过 4 次就是一吨)
- 该代码针对 Compact Framework
至少在其中两种情况下,我会使用 StringBuilder。
我根据可读性进行选择。当变量周围有一些文本时,我更喜欢格式选项。在这个例子中:
Console.WriteLine("User {0} accessed {1} on {2}.",
user.Name, fileName, timestamp);
即使没有变量名,你也能理解其含义,而 concat 中充斥着引号和 + 号,让我的眼睛感到困惑:
Console.WriteLine("User " + user.Name + " accessed " + fileName +
" on " + timestamp + ".");
(我借用了迈克的例子,因为我喜欢它)
如果没有变量名,格式字符串没有多大意义,我必须使用 concat:
Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
格式选项使我读取变量名称并将它们映射到相应的数字。concat 选项不需要这样做。我仍然对引号和+号感到困惑,但替代方案更糟糕。红宝石?
Console.WriteLine(p.FirstName + " " + p.LastName);
性能方面,我希望格式选项比连接慢,因为格式要求字符串是 解析的. 。我不记得必须优化这种指令,但如果我这样做了,我会看看 string
方法如 Concat()
和 Join()
.
format 的另一个优点是可以将格式字符串放入配置文件中。对于错误消息和 UI 文本非常方便。
如果您打算本地化结果,则 String.Format 至关重要,因为不同的自然语言甚至可能不具有相同顺序的数据。
我认为这在很大程度上取决于输出的复杂程度。我倾向于选择当时最有效的方案。
根据工作选择正确的工具:D 无论哪个看起来最干净!
我也更喜欢第二种,但目前我没有合理的论据来支持这一立场。
好一个!
刚刚添加
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,但第一个示例需要某种翻译)。
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
由于我认为这里的答案并不能涵盖所有内容,因此我想在这里做一些补充。
Console.WriteLine(string format, params object[] pars)
来电 string.Format
. 。“+”表示字符串连接。我认为这并不总是与风格有关;而是与风格有关。我倾向于根据我所处的环境混合这两种风格。
简短回答
您面临的决定与字符串分配有关。我会尽力让它变得简单。
说你有
string s = a + "foo" + b;
如果执行此命令,它将评估如下:
string tmp1 = a;
string tmp2 = "foo"
string tmp3 = concat(tmp1, tmp2);
string tmp4 = b;
string s = concat(tmp3, tmp4);
tmp
这里并不是真正的局部变量,而是 JIT 的临时变量(它被推送到 IL 堆栈上)。如果将字符串压入堆栈(例如 ldstr
在 IL 中表示文字),您将对字符串指针的引用放在堆栈上。
当你打电话的那一刻 concat
此引用成为一个问题,因为没有任何包含这两个字符串的可用字符串引用。这意味着.NET需要分配一个新的内存块,然后用两个字符串填充它。这是一个问题的原因是因为分配相对昂贵。
这将问题更改为:怎样才能减少数量 concat
运营?
所以,粗略的答案是: string.Format
对于 >1 个连接,“+”对于 1 个连接来说效果很好。如果你不关心进行微观性能优化, string.Format
在一般情况下工作得很好。
关于文化的注释
还有一种东西叫文化……
string.Format
使您能够使用 CultureInfo
在你的格式中。简单的运算符“+”使用当前区域性。
如果您正在编写文件格式和 f.ex,这一点尤其重要。 double
您“添加”到字符串的值。在不同的机器上,如果不使用,可能会得到不同的字符串 string.Format
具有明确的 CultureInfo
.
F. 前。考虑一下如果您更改'。对于“,”编写您的逗号分离值文件时...在荷兰语中,小数点分隔符是逗号,因此您的用户可能会得到一个“有趣”的惊喜。
更详细的答案
如果您事先不知道字符串的确切大小,最好使用这样的策略来过度分配您使用的缓冲区。首先填充空闲空间,然后复制数据。
增长意味着分配新的内存块并将旧数据复制到新的缓冲区。然后可以释放旧的内存块。此时你得到了底线:种植是一项昂贵的操作。
最实用的方法是使用超额分配策略。最常见的策略是以 2 的幂次过度分配缓冲区。当然,你必须做得更聪明一点(因为如果你已经知道你需要 128 个字符,那么从 1,2,4,8 开始增长是没有意义的),但你明白了。该策略确保您不需要我上面描述的太多昂贵的操作。
StringBuilder
是一个基本上以 2 的幂为单位过度分配底层缓冲区的类。 string.Format
用途 StringBuilder
在引擎盖下。
这使得您的决定成为过度分配和附加(-多个)(w/w.o.)之间的基本权衡。文化)或只是分配并附加。
就个人而言,第二个是您使用的所有内容都按照它将输出的直接顺序。而对于第一个,您必须将 {0} 和 {1} 与正确的变量相匹配,这很容易搞乱。
至少它不像 C++ sprintf 那么糟糕,如果变量类型错误,整个事情就会崩溃。
此外,由于第二个都是内联的,并且不需要对所有 {0} 内容进行任何搜索和替换,因此后者应该更快...虽然我不确定。
我实际上喜欢第一个,因为当文本中混有很多变量时,对我来说似乎更容易阅读。另外,使用 string.Format() 呃格式时处理引号更容易。这是 体面的分析 的字符串连接。
我一直走 string.Format() 路线。能够像 Nathan 的示例一样将格式存储在变量中是一个很大的优势。在某些情况下,我可能会附加一个变量,但一旦连接超过 1 个变量,我就会重构以使用格式。
哦,为了完整起见,以下代码比正常串联快了几个刻度:
Console.WriteLine(String.Concat(p.FirstName," ",p.LastName));
第一个(格式)对我来说看起来更好。它更具可读性,并且您不会创建额外的临时字符串对象。
我很好奇 StringBuilder 在这些测试中处于什么位置。结果如下...
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;
}
}
结果:
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
根据 MCSD 准备材料,Microsoft 建议在处理极少数连接(可能是 2 到 4 个)时使用 + 运算符。我仍然不确定为什么,但这是值得考虑的事情。