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


177

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

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

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

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

您是使用格式还是只连接字符串?什么是你最喜欢的?其中一种会伤害您的眼睛吗?

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

我会去第二个。

Answers:


88

试试这个代码。

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

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); 花费:618ms-2213706滴答声
1000000 x结果=(p.FirstName +“” + p.LastName); 耗时:166毫秒-595610滴答


1
很有意思。我得到的平均时间是224ms与48ms,x4.66有了改善,甚至比您的x3.72还好。我想知道是否有一个后编译工具可以重写IL string.Format,它不使用任何复合格式设置功能(即,简单的{0}),而是用相当快的字符串连接替换它们。我想知道,使用现有的IL重写器(例如PostSharp)是否可以实现这样的壮举。
Allon Guralnek 2011年

31
字符串是不可变的,这意味着一遍又一遍地在代码中使用相同的内存。一遍又一遍地添加相同的两个字符串并一次创建相同的新字符串不会影响内存。.Net足够聪明,可以使用相同的内存引用。因此,您的代码并没有真正测试两个concat方法之间的区别。请参阅下面我的答案中的代码。
拉丁顿

1
老实说,我总是连接起来,因为它对我来说更容易阅读,而且哇,它更快:)
puretppc 2014年

因此,速度是唯一选择一个的唯一原因?
niico

158

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

过早优化=失败。

我会选择该String.Format选项,只是因为从体系结构的角度来看它最有意义。在出现问题之前,我不会在意它的性能(如果确实如此,我会问自己:我是否需要一次串联一百万个名字?它们肯定不会全部显示在屏幕上...)

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


47
关于“过早优化==失败”的要点,是的。但是,当您开始为执行足迹付费(云和基础架构即服务,有人吗?)和/或开始为一百万个用户提供支持时,那么对请求中单个用户的响应就不是问题了。服务给用户的请求的成本是您的底线成本,也是规模问题,如果/当其他几千个呼叫正在通过时...
Aidanapword 2010年

23
这是完全错误的。在Web开发环境中,字符串生成代码通常会深深存在于模型,视图和控制器中,并且每次页面加载可能被称为数万次。将花费在评估字符串生成代码上的时间减少50%可能是一个巨大的胜利。
本杰明·萨斯曼

2
这样的问题不仅会在OP的一个实例中应用。答案是人们可以记住的那种东西:“我应该以哪种方式组装琴弦?” 当他们编写所有代码时。
Phil Miller

6
@Benjamin:...在这种情况下,您需要分析并发现这是您的瓶颈。我敢打赌,尽管如此,您只是从无到有。在过去编写和分析了许多Web应用程序之后,我几乎总是发现响应时间(在服务器端)的瓶颈是数据库查询。
BlueRaja-Danny Pflughoeft13年

2
这绝对不是过早的优化。相当谬论。字符串性能会完全使UI停滞,特别是在.NET中,如果您要进行大量格式化和字符串构建。ubiquity.acm.org/article.cfm?id=1513451
user99999991 2015年

54

哦,亲爱的-阅读其他回复之一后,我尝试了反转操作的顺序-因此请先执行串联,然后执行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 Stream的初始化,因为在第一个操作使所有时间恢复一致之前放置Console.Writeline(“ Start!”)。


2
然后从测试中完全删除Console.WriteLine。这使结果产生偏差!
CShark

出于这个确切的原因,在运行性能测试时,我总是从乱扔或“控制”的场景开始
drzaus

36

字符串是不可变的,这意味着一遍又一遍地在代码中使用相同的内存。一遍又一遍地添加相同的两个字符串并一次创建相同的新字符串不会影响内存。.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

1
在答案中添加了StringBuilder和示例输出
mikeschuld 2013年

我看到使用string.Format值得在这里达到微不足道的性能。从结构上讲,它更好,因为它意味着您可以更轻松地更改格式。但是我真的不明白stringbuilder的意义。这里的所有其他线程都说您应该使用Stringbuilder而不是连接字符串。有什么好处?正如该基准所证明的,显然不是速度。
roryok

22

可怜可怜的翻译

如果您知道您的应用程序将使用英语,那就很好,保存时钟滴答。但是,许多文化通常会在例如地址中看到“姓氏名”。

因此,请使用string.Format(),尤其是如果您要将应用程序放到英语不是第一语言的任何地方。


2
string.Format()在不同的文化中表现如何不同?它还会打印名字然后姓吗?在这两种情况下,您似乎都必须考虑到不同的文化。我觉得我在这里想念什么。
布鲁斯·韦恩布

2
我同意@DangerZone ..如何string.Format()知道您使用的是地址名称?如果根据文化进行string.Format()交换{0} {1},我会认为它是坏的。
亚历克斯·麦克米伦

2
我相信Jeremy试图提出的观点是,在描述为支持不同国家/地区的情况下,将格式字符串本身提取为语言资源可能是适当的。对于大多数国家/地区,该字符串应为“ {0} {1}”,但对于通常以姓氏为首的国家/地区(例如匈牙利,香港,柬埔寨,中国,日本,韩国,马达加斯加,台湾,越南和印度的部分地区),该字符串将改为“ {1} {0}”。
理查德·福斯特

确实。或者,更巧妙地,将格式字符串添加为人员的属性。例如,我喜欢以我的姓氏为姓,但我的同事Beng没有。
杰里米·麦基

14

这是我经过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");

因此,我不知道将谁的回复标记为答案:)


为什么此答案的背景为蓝色?
user88637 2009年

@yossi是蓝色的,因为应答者
询问者

9

在这样的简单情况下,连接字符串是很好的-它比任何复杂的事情都复杂,甚至包括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);

7

对于非常简单的操作,我将使用串联,但是一旦超出2或3个元素,Format就会变得更适合IMO。

选择String.Format的另一个原因是.NET字符串是不可变的,以这种方式创建的临时/中间副本更少。


6

虽然我完全理解样式首选项,并部分根据自己的喜好选择了第一个答案,但是我的部分决定是基于这样的想法,即串联会更快。因此,出于好奇,我对其进行了测试,结果令人st舌,尤其是对于这么小的字符串。

使用以下代码:

    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,这就是为什么我也输出定时器滴答的原因。


2
但是,当然您应该多次执行该操作才能获得测量结果。
erikkallen

2
并失去对Console.Writeline()的调用,因为它超出了问题的范围?
Aidanapword 2010年

你用stringbuilder测试了吗?;)
niico

6

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

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

内插的字符串表达式看起来像包含表达式的模板字符串。内插的字符串表达式通过将包含的表达式替换为表达式结果的ToString表示形式来创建字符串。

插值的字符串具有与String.Format相似的性能,但是由于将值和表达式插入行内,因此提高了可读性并缩短了语法。

另请参阅有关字符串内插的dotnetperls文章

如果您正在寻找格式化字符串的默认方式,那么就可读性和性能而言,这是有意义的(除非微秒会在您的特定用例中有所作为)。


5

对于基本的字符串连接,我通常使用第二种样式-易于阅读和简化。但是,如果我要进行更复杂的字符串组合,则通常会选择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 + ".");

仅保存了一些字符,但是在此示例中,我认为格式使它更加整洁。


5

更好的测试方法是使用Perfmon和CLR内存计数器监视您的内存。我的理解是,您想使用String.Format而不是仅连接字符串的全部原因是,由于字符串是不可变的,因此不必要地给垃圾收集器增加了临时字符串,这些字符串需要在下一遍中回收。

尽管StringBuilder和String.Format可能更慢,但它们的内存效率更高。

字符串串联有什么不好?


我同意; 每个字符串操作都会创建该字符串的新副本。所有这些内存迟早都会被垃圾收集器回收。因此,分配大量字符串可能稍后会再次咬住您。
Marnix van Valen

5

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

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

但是应注意,如果要传递给String.Format的参数(以及诸如Console.Write之类的其他此类方法)是值类型,则它们将在传递之前被装箱,这可以提供其自身的性能命中率。关于此的博客文章


1
该博客文章现在位于:jeffbarnes.net/blog/post/2006/08/08/…。我的代表编辑不足。
理查德·斯莱特

5

从2015年8月19日起一周,这个问题将是正确的七(7)年。现在有一种更好的方法。在可维护性方面更好,因为与串联字符串相比,我没有进行任何性能测试(但这几天有关系吗?相差几毫秒?)。使用C#6.0的新方法:

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

这个新功能更好,IMO,实际上对我们来说更好,因为我们有代码来构建查询字符串,其值取决于某些因素。想象一个有6个参数的querystring。因此,例如:

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的新方法比以前的替代方法更好。
菲利普

那就对了。而且它也更安全,因为您不必担心哪个对象将转到哪个索引(占位符),因为您可以将对象直接放置在想要的位置。
冯诉

顺便说一句,它实际上调用了Format(至少使用Roslyn)。
菲利普(Philippe)

顺便说一句,此发布者指的是“字符串插值”,在此线程的其他地方都有介绍。
CShark

4
  1. 格式化是“ .NET”的一种实现方式。某些重构工具(其中之一就是Refactor!)甚至会提议重构concat样式的代码以使用格式化样式。
  2. 格式化更易于为编译器优化(尽管第二种可能会重构为使用快速的“ Concat”方法)。
  3. 格式通常更清晰易读(尤其是“花式”格式)。
  4. 格式化意味着在所有变量上隐式调用'.ToString',这有利于提高可读性。
  5. 根据“有效的C#” ,. NET的“ WriteLine”和“格式”实现被搞砸了,它们将所有值类型自动装箱(这很不好)。“有效的C#”建议明确地执行“ .ToString”调用,恕我直言,这是虚假的(请参阅Jeff的文章
  6. 目前,编译器未检查格式类型提示,从而导致运行时错误。但是,可以在以后的版本中对此进行修改。

4

我选择基于可读性。当变量周围有一些文本时,我更喜欢格式选项。在此示例中:

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);

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

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


4

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

如果要显示给用户,我将使用String.Format以便可以本地化-FxCop会为我拼写检查,以防万一:)

如果它包含数字或任何其他非字符串形式的内容(例如日期),我将使用String.Format,因为它使我可以更好地控制formatting

如果是用于构建类似SQL的查询,则可以使用Linq

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

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


3

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

  • 该代码需要执行数百万次
  • 您正在做大量的连翘(超过4吨是一吨)
  • 该代码针对紧凑框架

在至少两种情况下,我将改用StringBuilder。


3

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


2

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

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


2

我也喜欢第二个,但目前我没有理性的论据来支持这一立场。


2

好一个!

刚刚添加

        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

2
由于基于运算符的字符串串联被编译器转换为调用,因此花费的时间完全相同string.Concat(...)。它是在编译期间完成的,因此对运行时性能没有影响。如果您多次运行测试或在更大的测试样本上运行它们,您会发现它们是相同的。
Allon Guralnek 2011年

2

由于我认为此处的答案无法涵盖所有​​内容,因此我想在此处添加一些内容。

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个concat,“ +”将对1个concat正常工作。而且,如果您不关心微性能优化,string.Format在一般情况下也可以正常工作。

关于文化的笔记

还有一种叫做文化的东西

string.Format使您可以使用CultureInfo格式。一个简单的运算符“ +”使用当前的区域性。

如果要编写文件格式和f.ex,这尤其重要。double您“添加”到字符串的值。在不同的机器上,如果不使用string.Formatexplicit ,可能会以不同的字符串结尾CultureInfo

F.ex. 考虑一下如果更改“。”会发生什么。对于“,”,则在编写您的逗号分隔值文件时……用荷兰语,十进制分隔符是一个逗号,因此您的用户可能会得到一个“有趣”的惊喜。

更详细的答案

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

增长意味着分配新的内存块并将旧数据复制到新缓冲区。然后可以释放旧的内存块。您现在已经掌握了底线:增长是一项昂贵的操作。

执行此操作的最实用方法是使用过度分配策略。最常见的策略是以2的幂来整体分配缓冲区。 ),但您得到了图片。该策略确保您不需要我在上面描述的太多昂贵的操作。

StringBuilder是一类,基本上以2的幂次占用基础缓冲区。在引擎盖下string.Format使用StringBuilder

这使您的决定成为总体分配与追加(多个)(无文化)或分配分配与追加之间的基本权衡。


1

就个人而言,您正在使用的所有东西的第二个都将以直接顺序输出。而第一个则必须将{0}和{1}与适当的var匹配,这很容易弄乱。

至少它不如C ++ sprintf差,如果您弄错了变量类型,整个事情将会崩溃。

另外,由于第二个都是内联的,并且不必为所有{0}进行搜索和替换,因此后者应该更快...尽管我不确定。


1

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


1

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


1

哦,为了完整起见,以下内容比普通的连接速度快了几步:

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

1

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


1

我很好奇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;
   }
}

结果:

康卡特:406滴答
康卡特:356滴答
康卡特:411滴答
康卡特:299滴答
康卡特:266滴答
格式:5269 ticks
格式:954 ticks
格式:1004 ticks
格式:984 ticks
格式:974
StringBuilder:629分钟
StringBuilder:484个报价
StringBuilder:482个滴答
StringBuilder:508分钟
StringBuilder:504分钟

1

根据MCSD准备材料,Microsoft建议在处理极少数串联(可能是2到4)时使用+运算符。我仍然不确定为什么,但这是需要考虑的事情。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.