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

Gav*_*ler 113 .net c# string

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

有什么区别

xlsSheet.Write("C" + rowIndex.ToString(), null, title);
Run Code Online (Sandbox Code Playgroud)

xlsSheet.Write(string.Format("C{0}", rowIndex), null, title);
Run Code Online (Sandbox Code Playgroud)

这个比那个好吗?为什么?

Dan*_* C. 154

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

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

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

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

想法是.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;
}
Run Code Online (Sandbox Code Playgroud)

对此:

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 });
}
Run Code Online (Sandbox Code Playgroud)

在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)使用ReSharper时`string.Format`是"安全的"; 也就是说,它与[错误]可以使用的任何其他代码一样安全.2)`string.Format`*确实*允许"安全"`null`:`string.Format("A {0} B",(string)null)`结果为"AB".3)我很少关心这个级别的性能(为此,当我拉出`StringBuilder`时,这是一个罕见的日子*)... (5认同)
  • @RichardCollette是的,String.Concat甚至用于连接方法调用的返回值,例如`string s ="This"+ MyMethod(arg)+"是一个测试";`被编译为`String.Concat() `在发布模式下呼叫. (2认同)

Jon*_*eet 110

在C#6之前

说实话,我认为第一个版本更简单 - 虽然我将它简化为:

xlsSheet.Write("C" + rowIndex, null, title);
Run Code Online (Sandbox Code Playgroud)

我怀疑其他答案可能会谈到性能损失,但说实话,如果存在,它将是最小的- 并且这个连接版本不需要解析格式字符串.

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

用C#6

字符串插值使C#6中的大部分内容更容易阅读.在这种情况下,您的第二个代码变为:

xlsSheet.Write($"C{rowIndex}", null, title);
Run Code Online (Sandbox Code Playgroud)

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

  • @nawfal:见http://msmvps.com/blogs/jon_skeet/archive/2008/10/08/why-boxing-doesn-t-keep-me-awake-at-nights.aspx (6认同)
  • 既然C#6可用,你可以使用新的字符串插值语法,我认为更容易阅读:`xlsSheet.Write($"C {rowIndex}",null,title);` (3认同)
  • @ mbomb007:现在位于http://codeblog.jonskeet.uk/2008/10/08/why-boxing-doesn-t-keep-me-awake-at-nights/ (2认同)

Mar*_*rth 6

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

xlsSheet.Write("C" + rowIndex.ToString(), null, title);
Run Code Online (Sandbox Code Playgroud)

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

无论哪种方式,首先编写可读性,然后使用性能分析器识别您的热点,如果您真的认为您有性能问题.


P D*_*ddy 5

对于一个简单的单一连接的简单情况,我觉得它不值得复杂string.Format(并且我没有测试过,但我怀疑对于像这样的简单情况,string.Format 可能会稍微慢一点,格式字符串解析是什么和所有).和Jon Skeet一样,我更喜欢不显式调用.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]
);
Run Code Online (Sandbox Code Playgroud)

更新

在我看来,我给出的例子有点令人困惑,因为看起来我在这里使用连接string.Format.是的,从逻辑上和词汇上来说,这就是我所做的.但是这些连接都将被编译器1优化掉,因为它们都是字符串文字.所以在运行时,会有一个字符串.所以我想我应该说我更喜欢在运行时避免很多连接.

当然,这个主题的大部分内容现在都已过时,除非您仍然使用C#5或更早版本.现在我们已经插入了字符串,为了便于阅读,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"
);
Run Code Online (Sandbox Code Playgroud)

你这样做会失去编译时连接. 每个插值字符串都会转换为string.Format编译器的调用,并且它们的结果在运行时连接在一起.这意味着这是对可读性的运行时性能的牺牲.大多数时候,这是值得的牺牲,因为运行时间的惩罚可以忽略不计.但是,在性能关键代码中,您可能需要分析不同的解决方案.


1 您可以在C#规范中看到这一点:

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

...

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

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

const string s =
    "This compiles successfully, " +
    "and you can see that it will " +
    "all be one string (named `s`) " +
    "at run time";
Run Code Online (Sandbox Code Playgroud)

  • 哇,每个人都专注于字符串文字,而不是问题的核心. (2认同)