什么时候使用String.Format vs字符串连接更好?

时间:2008-11-17 21:20:33

标签: c# .net string

我有一小段代码正在解析索引值以确定输入Excel的单元格。这让我想到了......

之间有什么区别
xlsSheet.Write("C" + rowIndex.ToString(), null, title);

xlsSheet.Write(string.Format("C{0}", rowIndex), null, title);

一个人比另一个“更好”吗?为什么?

14 个答案:

答案 0 :(得分:152)

我最初的偏好(来自C ++背景)是针对String.Format的。由于以下原因,我稍后放弃了这个:

  • 字符串连接可以说是“更安全”。它发生在我身上(我已经看到它发生在其他几个开发人员身上)删除参数,或者错误地搞乱了参数顺序。编译器不会根据格式字符串检查参数,最终会出现运行时错误(也就是说,如果你很幸运,不要在一个模糊的方法中使用它,例如记录错误)。通过连接,删除参数不易出错。你可以说错误的可能性很小,但可能发生。

- 字符串连接允许空值,String.Format不允许。写“s1 + null + s2”不会中断,它只是将null值视为String.Empty。嗯,这可能取决于您的具体情况 - 有些情况下您需要一个错误而不是默默地忽略null FirstName。然而,即使在这种情况下,我个人更喜欢自己检查空值并抛出特定错误,而不是我从String.Format获得的标准ArgumentNullException。

  • 字符串连接执行得更好。上面的一些帖子已经提到了这一点(没有实际解释为什么,这决定了我写这篇文章:)。

Idea是.NET编译器足够聪明,可以转换这段代码:

public static string Test(string s1, int i2, int i3, int i4, 
        string s5, string s6, float f7, float f8)
{
    return s1 + " " + i2 + i3 + i4 + " ddd " + s5 + s6 + f7 + f8;
}

到此:

public static string Test(string s1, int i2, int i3, int i4,
            string s5, string s6, float f7, float f8)
{
    return string.Concat(new object[] { s1, " ", i2, i3, i4, 
                    " ddd ", s5, s6, f7, f8 });
}

在String.Concat的引擎下发生的事情很容易猜到(使用Reflector)。数组中的对象通过ToString()转换为其字符串。然后计算总长度并且仅分配一个字符串(具有总长度)。最后,在一些不安全的代码片段中,每个字符串都会通过wstrcpy复制到结果字符串中。

原因String.Concat更快?好吧,我们都可以看看String.Format正在做什么 - 您会对处理格式字符串所需的代码量感到惊讶。除此之外(我见过关于内存消耗的评论),String.Format在内部使用了StringBuilder。方法如下:

StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));

因此,对于每个传递的参数,它保留8个字符。如果参数是一位数的值,那么太糟糕了,我们有一些浪费的空间。如果参数是在ToString()上返回一些长文本的自定义对象,那么甚至可能需要一些重新分配(当然,最糟糕的情况)。

与此相比,连接只会浪费对象数组的空间(不要太多,考虑到它是一个引用数组)。格式说明符没有解析,也没有中间StringBuilder。两种方法都存在装箱/拆箱开销。

我选择String.Format的唯一原因是涉及本地化时。将格式字符串放在资源中允许您支持不同的语言而不会弄乱代码(考虑格式化值根据语言改变顺序的情况,即“{0}小时和{1}分钟后”在日语中看起来可能大不相同: )。


总结我的第一篇(也是很长篇)帖子:

  • 对我来说,最佳方式(在性能与可维护性/可读性方面)使用字符串连接,没有任何ToString()调用
  • 如果你在表演之后,自己做ToString()电话以避免拳击(我有点偏向于可读性) - 与问题中的第一个选项相同
  • 如果您向用户显示本地化字符串(此处不是这种情况),则String.Format()有优势。

答案 1 :(得分:105)

在C#6之前

老实说,我认为第一个版本更简单 - 尽管我将其简化为:

xlsSheet.Write("C" + rowIndex, null, title);

我怀疑其他答案可能谈论性能损失,但说实话,如果存在,它将是最小的 - 并且此级联版本不需要解析格式字符串。

格式化字符串非常适合本地化等目的,但在这种情况下,连接更简单,也可以正常工作。

使用C#6

字符串插值使得在C#6中读取的内容更加简单。在这种情况下,您的第二个代码变为:

xlsSheet.Write($"C{rowIndex}", null, title);

这可能是最好的选择,IMO。

答案 2 :(得分:5)

我认为第一个选项更具可读性,应该是您最关心的问题。

xlsSheet.Write("C" + rowIndex.ToString(), null, title);

string.Format在底层使用StringBuilder(使用reflector检查),因此除非您进行大量连接,否则它不会有任何性能优势。对于您的场景来说速度会慢一些,但实际情况是这种微观性能优化决策在大多数情况下是不合适的,除非您处于循环中,否则您应该专注于代码的可读性。

无论哪种方式,首先编写可读性,然后使用performance profiler标识您的热点,如果您确实认为自己存在性能问题。

答案 3 :(得分:5)

对于一个简单的单一连接的简单情况,我觉得它不值得string.Format的复杂性(我没有测试过,但我怀疑对于像这样的简单情况,{{1 可能稍微慢一些,格式化字符串解析和所有)。和Jon Skeet一样,我更喜欢不明确地调用string.Format,因为这将由.ToString()重载隐式完成,我认为代码看起来更干净,更容易阅读而没有它。

但是对于多个连接(多少是主观的),我绝对更喜欢string.Concat(string, object)。在某个时刻,我认为可读性和性能都会因连接而受到不必要的影响。

如果格式字符串有很多参数(同样,“很多”是主观的),我通常更喜欢在替换参数上包含注释索引,以免我忘记哪个值转到哪个参数。一个人为的例子:

string.Format

更新

在我看来,我给出的示例有点令人困惑,因为我似乎在这里使用了两个连接和Console.WriteLine( "Dear {0} {1},\n\n" + "Our records indicate that your {2}, \"{3}\", is due for {4} {5} shots.\n" + "Please call our office at 1-900-382-5633 to make an appointment.\n\n" + "Thank you,\n" + "Eastern Veterinary", /*0*/client.Title, /*1*/client.LastName, /*2*/client.Pet.Animal, /*3*/client.Pet.Name, /*4*/client.Pet.Gender == Gender.Male ? "his" : "her", /*5*/client.Pet.Schedule[0] ); 。是的,从逻辑上和词汇上来说,这就是我所做的。但是这些连接都将被编译器 1 优化掉,因为它们都是字符串文字。所以在运行时,会有一个字符串。所以我想我应该说我更喜欢在运行时避免许多连接

当然,这个主题的大部分内容现在都已过时,除非你仍然坚持使用C#5或更早版本。现在我们有interpolated strings,为了便于阅读,几乎在所有情况下都优于string.Format。这些天,除非我只是将值直接连接到字符串文字的开头或结尾,否则我几乎总是使用字符串插值。今天,我会像这样写下我之前的例子:

string.Format

这样就失去了编译时串联。编译器会将每个插值字符串转换为对Console.WriteLine( $"Dear {client.Title} {client.LastName},\n\n" + $"Our records indicate that your {client.Pet.Animal}, \"{client.Pet.Name}\", " + $"is due for {(client.Pet.Gender == Gender.Male ? "his" : "her")} " + $"{client.Pet.Schedule[0]} shots.\n" + "Please call our office at 1-900-382-5633 to make an appointment.\n\n" + "Thank you,\n" + "Eastern Veterinary" ); 的调用,并且它们的结果在运行时连接在一起。这意味着这是对可读性的运行时性能的牺牲。大多数时候,这是值得的牺牲,因为运行时间的惩罚可以忽略不计。但是,在性能关键代码中,您可能需要分析不同的解决方案。


<子> 1 您可以在the C# specification

中看到这一点
  

...在常量表达式中允许使用以下结构:

     

...

     
      
  • 预定义的+ ...二元运算符...
  •   

您也可以使用一些代码验证它:

string.Format

答案 4 :(得分:3)

如果您的字符串更复杂,并且连接了许多变量,那么我会选择string.Format()。但是对于字符串的大小和在你的情况下连接的变量的数量,我会使用你的第一个版本,它更多spartan

答案 5 :(得分:3)

我看了一下String.Format(使用Reflector),它实际上创建了一个StringBuilder,然后在其上调用AppendFormat。因此,它比连续多次搅拌更快。最快(我相信)将创建一个StringBuilder并手动调用Append。当然,“很多”的数量可供猜测。 我会使用+(实际上&amp;因为我主要是VB程序员)来做一些像你的例子一样简单的事情。随着它变得越来越复杂,我使用String.Format。如果有很多变量,那么我会选择StringBuilder和Append,例如,我们有代码构建代码,在那里我使用一行实际代码输出一行生成的代码。

似乎有一些关于为每个操作创建了多少字符串的猜测,所以让我们举几个简单的例子。

"C" + rowIndex.ToString();

“C”已经是一个字符串 rowIndex.ToString()创建另一个字符串。 (@manohard - 不会发生rowIndex拳击)
然后我们得到最后的字符串。
如果我们以

为例
String.Format("C(0)",rowIndex);

然后我们将“C {0}”作为字符串
rowIndex被装箱以传递给函数
创建一个新的stringbuilder 在字符串构建器上调用AppendFormat - 我不知道AppendFormat如何运行的细节,但我们假设它是超高效的,它仍然需要将盒装的rowIndex转换为字符串。
然后将stringbuilder转换为新的字符串 我知道StringBuilders试图阻止无意义的内存副本发生,但是与普通连接相比,String.Format仍然会产生额外的开销。

如果我们现在举几个字符串的例子

"a" + rowIndex.ToString() + "b" + colIndex.ToString() + "c" + zIndex.ToString();

我们有6个字符串开头,对所有情况都是一样的。
使用连接,我们还有4个中间字符串加上最终结果。通过使用String,Format(或StringBuilder)消除了那些中间结果 请记住,要创建每个中间字符串,必须将前一个字符串复制到新的内存位置,而不仅仅是内存分配可能很慢。

答案 6 :(得分:2)

这个例子可能太微不足道,无法发现差异。事实上,我认为在大多数情况下,编译器可以完全消除任何差异。

然而,如果我不得不猜测我会给string.Format()更复杂的场景带来优势。但这更像是一种直觉,它可能会更好地利用缓冲区而不是产生多个不可变字符串,而不是基于任何真实数据。

答案 7 :(得分:1)

我喜欢String.Format,因为它可以使您的格式化文本比内联连接更容易跟踪和阅读,而且它更灵活,允许您格式化您的参数,但是对于像您这样的简短用途,我认为没有关于连接的问题。 / p>

对于循环内部或大字符串中的连接,您应该总是尝试使用StringBuilder类。

答案 8 :(得分:1)

我同意上面的很多观点,我认为应该提到的另一点是代码可维护性。 string.Format允许更容易地更改代码。

即。我有个消息 "The user is not authorized for location " + location"The User is not authorized for location {0}"

如果我想更改消息说: location + " does not allow this User Access""{0} does not allow this User Access"

使用string.Format所有我需要做的就是更改字符串。 对于连接,我必须修改该消息

如果在多个地方使用可以节省一些时间。

答案 9 :(得分:1)

我的印象是string.format更快,在这个测试中似乎慢了3倍

(function() {
  window.addEventListener("load", foo, false);

  function foo() {
    var u = "beforeunload";
    var v = unsafeWindow;
    if (v._eventTypes && v._eventTypes[u]) {
      var r=v._eventTypes[u];
      for(var s=0;s<r.length;s++) {
        v.removeEventListener(u,r[s],false);
      }
      v._eventTypes[u]=[];
    }
  } 

})();

string.format耗时4.6秒,使用&#39; +&#39;花了1.6秒。

答案 10 :(得分:0)

当格式模板(“C {0}”)存储在配置文件(例如Web.config / App.config)中时,

string.Format可能是更好的选择

答案 11 :(得分:0)

我对各种字符串方法进行了一些分析,包括string.Format,StringBuilder和字符串连接。字符串连接几乎总是优于构建字符串的其他方法。所以,如果性能是关键,那就更好了。但是,如果性能不是很关键,那么我个人觉得string.Format在代码中更容易理解。 (但这是一个主观原因)但是,StringBuilder在内存利用率方面可能是最有效的。

答案 12 :(得分:0)

我更喜欢String.Format关于性能

答案 13 :(得分:-1)

与String.Format相比,字符串连接需要更多内存。因此,连接字符串的最佳方法是使用String.Format或System.Text.StringBuilder Object。

让我们来看第一种情况:“C”+ rowIndex.ToString() 假设rowIndex是一个值类型,因此ToString()方法必须将Box转换为String,然后CLR为包含两个值的新字符串创建内存。

当string.Format需要对象参数并将rowIndex作为对象并将其转换为字符串内部时,会有Boxing但它是内在的,并且它不会占用与第一种情况相同的内存。

对于短小的字符串,我猜不会那么重要......