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

时间:2020-03-05 18:40:46  来源:igfitidea点击:

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

  • 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(),尤其是如果我们要将应用程序放到英语不是第一语言的任何地方时。