字符串输出:C#中的格式还是concat?
假设我们要输出或者连接字符串。我们更喜欢以下哪种风格?
- var p = new {FirstName =" Bill",LastName =" Gates"};`
Console.WriteLine(" {0} {1}",p.FirstName,p.LastName);
Console.WriteLine(p.FirstName +"" + p.LastName);
我们是使用格式还是只连接字符串?什么是你最喜欢的?其中之一会伤害眼睛吗?
我们有任何理性的论据来使用一种而不是另一种吗?
我会去第二个。
解决方案
回答
就个人而言,我们正在使用的所有内容的第二个都将以直接顺序输出。而第一个则必须将{0}和{1}与适当的var匹配,这很容易弄乱。
至少它不如C ++ sprintf差,如果我们弄错了变量类型,整个事情将会崩溃。
另外,由于第二个都是内联的,并且不必为所有{0}进行搜索和替换,因此后者应该更快...尽管我不确定。
回答
我认为这在很大程度上取决于输出的复杂程度。我倾向于选择当时最合适的方案。
根据工作选择合适的工具:D哪一个看起来最干净!
回答
对于非常简单的操作,我将使用串联,但是一旦超出2或者3个元素,Format就会变得更适合IMO。
首选String.Format的另一个原因是.NET字符串是不可变的,以这种方式进行操作会创建较少的临时/中间副本。
回答
我也更喜欢第二个,但目前我没有合理的论据来支持这一立场。
回答
通常,我更喜欢前者,因为特别是当字符串变长时,它更容易阅读。
我相信另一个好处是性能,因为后者实际上在将最终字符串传递给Console.Write方法之前执行了2个字符串创建语句。我相信String.Format在底层使用了StringBuilder,因此避免了多个串联。
但是应该注意,如果要传递给String.Format的参数(以及其他类似Console.Write的方法)是值类型,则将它们装箱后再传递,这可以提供其自身的性能命中率。关于此的博客文章。
回答
对于基本的字符串连接,我通常使用更易于阅读和简化的第二种样式。但是,如果我要进行更复杂的字符串组合,则通常会选择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 + ".");
仅保存了几个字符,但是在此示例中,我认为格式使它更加整洁。
回答
在一个简单的场景中,连接字符串就可以了,例如,它的复杂性比其他任何事物都要复杂,甚至包括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);
回答
我实际上很喜欢第一个,因为当有很多变量与文本混合在一起时,对我来说似乎更容易理解。另外,使用string.Format(),呃,格式时,使用引号会更容易。这是字符串连接的体面分析。
回答
- 格式化是.NET的实现方式。某些重构工具(其中之一就是Refactor!)甚至会提议重构concat样式的代码以使用格式化样式。
- 格式化更容易为编译器优化(尽管第二种可能会重构为使用快速的" Concat"方法)。
- 格式化通常更容易阅读(尤其是花式格式化)。
- 格式化意味着在所有变量上隐式调用'.ToString',这有利于可读性。
- 根据有效的C#,. NET的" WriteLine"和"格式"实现被搞砸了,它们将所有值类型自动装箱(这很不好)。有效的C#建议显式执行'.ToString'调用,恕我直言,这是虚假的(请参阅Jeff的文章)
- 目前,编译器未检查格式类型提示,从而导致运行时错误。但是,可以在以后的版本中对此进行修改。
回答
虽然我完全理解样式首选项,并部分根据自己的喜好选择了第一个答案,但是我的部分决定是基于这样的想法,即串联会更快。因此,出于好奇,我对其进行了测试,结果令人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,这就是为什么我也输出定时器滴答的原因。
回答
我将使用String.Format,但我还将在资源文件中使用格式字符串,以便可以将其本地化为其他语言。使用简单的字符串concat不允许我们这样做。显然,如果我们永远不需要本地化该字符串,这都不是考虑的理由。这实际上取决于字符串的用途。
如果要显示给用户,我将使用String.Format,以便可以本地化,FxCop将为我进行拼写检查,以防万一:)
如果它包含数字或者任何其他非字符串形式的内容(例如日期),我将使用String.Format,因为它可以让我更好地控制格式。
如果是用于构建类似SQL的查询,则可以使用Linq。
如果要在循环内连接字符串,我将使用StringBuilder以避免性能问题。
如果它用于某些输出,用户将看不到,并且不会影响性能,那么我将使用String.Format,因为无论如何我都习惯使用它,而我只是习惯了它:)
回答
我总是走过string.Format()路线。能够像Nathan的示例一样在变量中存储格式是一个很大的优势。在某些情况下,我可能会添加一个变量,但是一旦连接了多个变量,我就会重构为使用格式。
回答
噢,为了完整起见,以下内容比普通的连接速度快了几个刻:
Console.WriteLine(String.Concat(p.FirstName," ",p.LastName));
回答
亲爱的,在阅读其他答复之一之后,我尝试将操作顺序颠倒过来,因此先执行串联,然后执行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!")。
回答
实际上,我昨天进行了这些测试,但是已经很晚了,所以我没有发表回答。
底线似乎是他们平均花费相同的时间。我做了超过100000次迭代的测试。
我还将尝试使用StringBuilder,回到家时,我将发布代码和结果。
回答
这是我经过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");
因此,我不知道将谁的回复标记为答案:)
回答
试试这个代码。
这是代码的略微修改版本。
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 result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 618ms - 2213706 ticks 1000000 x result = (p.FirstName + " " + p.LastName); took: 166ms - 595610 ticks
回答
好东西!
刚刚添加
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
回答
第一个(格式)对我来说看起来更好。它更具可读性,并且我们不会创建额外的临时字符串对象。
回答
令我惊讶的是,这么多人立即想找到执行速度最快的代码。如果一百万次迭代仍然需要不到一秒钟的时间来处理,那么最终用户是否会注意到这一点?不太可能。
Premature optimization = FAIL.
我会选择String.Format
选项,只是因为从体系结构的角度来看,它是最有意义的。在出现问题之前,我不会在意它的性能(如果确实如此,我会问自己:我是否需要一次串联一百万个名字?它们肯定不会全部显示在屏幕上...)
考虑一下客户以后是否想要更改它,以便他们可以配置是显示""姓氏的名字"还是"姓氏,姓氏"。使用"格式"选项,只需换出格式字符串,这很容易。使用concat,我们将需要额外的代码。当然,在这个特定示例中这听起来没什么大不了,但是可以推断出来。
回答
更好的测试是使用Perfmon和CLR内存计数器监视内存。我的理解是,我们想使用String.Format而不是仅连接字符串的全部原因是,由于字符串是不可变的,因此不必要地给垃圾收集器增加了临时字符串,这些字符串需要在下一遍中回收。
尽管StringBuilder和String.Format可能会更慢,但它们的内存效率更高。
字符串串联有什么不好?
回答
如果我们要处理的东西需要易于阅读(这是大多数代码),那么我会坚持使用运算符重载版本,除非:
- 该代码需要执行数百万次
- 我们正在做大量的连翘(超过4吨是一吨)
- 该代码针对紧凑框架
在至少两种情况下,我将改用StringBuilder。
回答
我很好奇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
回答
我选择基于可读性。
当变量周围有一些文本时,我更喜欢格式选项。在此示例中:
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);
format选项使我可以读取变量名称并将其映射到相应的数字。 concat选项不需要。我仍然对引号和+号感到困惑,但替代方法更糟。红宝石?
Console.WriteLine(p.FirstName + " " + p.LastName);
在性能方面,我希望format选项比concat慢,因为format需要解析字符串。我不记得必须优化这种指令,但是如果这样做的话,我会看一下诸如Concat()和Join()之类的string方法。
格式的另一个优点是可以将格式字符串放在配置文件中。错误消息和UI文本非常方便。
回答
如果要对结果进行本地化,则String.Format是必不可少的,因为不同的自然语言甚至可能没有相同顺序的数据。
回答
根据MCSD准备材料,Microsoft建议在处理极少数串联(可能是2到4)时使用+运算符。我仍然不确定为什么,但这是需要考虑的事情。
回答
可怜可怜的翻译
如果我们知道应用程序将继续使用英语,则可以保存时钟滴答。但是,许多文化通常会在例如地址中看到"姓氏名"。
因此,请使用string.Format()
,尤其是如果我们要将应用程序放到英语不是第一语言的任何地方时。