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

假设您想要输出或连接字符串。您更喜欢下列哪种样式?

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

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

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

您更愿意使用格式还是仅仅连接字符串?你最喜欢什么?其中一个伤到你的眼睛了吗?

你有什么理性的论据可以用一个而不用另一个吗?

我会选第二个。

99389 次浏览

就个人而言,第二个是你使用的所有东西都是按照输出顺序排列的。而对于第一个变量,您必须将{0}和{1}与适当的 var 相匹配,这很容易搞砸。

至少它没有 C + + sprint 那么糟糕,如果你把变量的类型弄错了,整个事情就完蛋了。

另外,因为第二个是内联的,并且它不需要对所有{0}的内容进行任何搜索和替换,所以后者应该会更快... ... 尽管我不确定。

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

根据工作选择正确的工具: D 看起来最干净的那个!

对于非常简单的操作,我会使用连接,但一旦你超过2或3个元素格式变得更适合 IMO。

另一个喜欢 String 的原因。格式就是这样。NET 字符串是不可变的,这样做会创建更少的临时/中间副本。

我也倾向于第二种,但是目前我没有理性的论据来支持这一立场。

一般来说,我更喜欢前者,特别是当字符串变长时,它可以更容易阅读。

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

但是,应该注意的是,如果您传递到 String.Format(和其他类似 Console 的方法)的参数。Write)是值类型,然后在传入之前将它们装箱,这可以提供自己的性能命中。在这里发布博客.

对于基本的字符串连接,我通常使用第二种样式——更容易阅读和更简单。但是,如果要进行更复杂的字符串组合,我通常选择 String。格式。

字符串。格式保存了大量的引号和加号..。

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

实际上我喜欢第一个,因为当有很多变量与文本混杂在一起时,对我来说读起来似乎更容易。另外,在使用字符串时处理引号更容易。格式,呃,格式。下面是字符串串联的 分析得不错

  1. 格式是“。NET 的方法。某些重构工具(重构!其中之一)甚至建议重构 concat 样式的代码以使用格式化样式。
  2. 格式化对于编译器来说更容易优化(尽管第二种格式可能会被重构以使用快速的‘ Concat’方法)。
  3. 格式设置通常更易于阅读(特别是“花式”格式设置)。
  4. 格式化意味着对所有变量隐式调用“ . ToString”,这有利于提高可读性。
  5. 根据“有效 C #”,。NET 的“ WriteLine”和“ Format”实现是混乱的,他们自动框所有值类型(这是坏的)。“有效 C #”建议执行”。显式地调用 ToString,其中 IMHO 是伪造的(参见 杰夫的帖子)
  6. 目前,编译器不检查格式化类型提示,从而导致运行时错误。但是,这可以在以后的版本中进行修改。

虽然我完全理解风格偏好,并且在一定程度上根据自己的偏好选择了连接作为我的第一个答案,但是我的决定部分是基于这样的想法,即连接会更快。因此,出于好奇,我测试了它,结果是惊人的,特别是对于这样一个小字符串。

使用以下代码:

    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多倍! !连接甚至没有注册为1毫秒,这就是为什么我输出计时器的滴答声。

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

如果要显示给用户,我会使用 String.Format,这样我就可以在需要的时候进行本地化,而且 FxCop会帮我检查拼写,以防万一:)

如果它包含数字或任何其他非字符串的东西(例如日期) ,我会使用 String.Format,因为它给我更多的 控制格式

如果是为了构建像 SQL 这样的查询,我会使用 Linq

如果要在循环中连接字符串,我会使用 StringBuilder来避免性能问题。

如果输出是用户看不到的,也不会影响性能,我会使用 String。格式,因为我已经养成了使用它的习惯,而且我已经习惯了:)

我总是走线。格式()路线。能够将格式存储在诸如 Nathan 的示例这样的变量中是一个很大的优势。在某些情况下,我可以附加一个变量,但一旦多于1个变量被连接,我重构使用格式化。

哦,为了完整起见,下面的连接比正常的连接快几拍:

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

哦,亲爱的——在读了其他的一个回复之后,我试着颠倒操作的顺序——所以首先执行连接,然后执行 String。格式..。

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个方法)增量更快。最快的似乎是控制台。WriteLine (String.Concat (...)方法,然后是正常的连接,然后是格式化操作。

启动的初始延迟很可能是 Console Stream 的初始化,比如放置一个 Console。Writeline (“开始!”)在第一次手术之前让所有的时间回到正轨。

事实上,我昨天做了这些测试,但是太晚了,所以我没有把我的答案。

底线似乎是它们平均花费相同的时间。我做了超过100000次迭代的测试。

我也会尝试使用 StringBuilder,回家后我会发布代码和结果。

以下是我在10万次迭代中得到的结果:

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毫秒会发生什么。在这两种情况下,除以1,000,000得到的结果都是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 100000 x result = string. Format (“{0}{1}”,p. FirstName,p. LastName) ; take: 618ms-2213706 ticks
1000000 x result = (p. FirstName + “”+ p. LastName) ; take: 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();

它甚至更快(我猜字符串。两个示例都调用了 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

第一个(格式)对我来说看起来更好。它更具可读性,而且你没有创建额外的临时字符串对象。

令我惊讶的是,这么多人马上就想找到执行速度最快的代码

过早优化 = 失败。

我会选择 String.Format选项,只是因为从架构的角度来看它是最有意义的。我不关心性能,直到它成为一个问题(如果它成为一个问题,我会问自己: 我是否需要一次连接一百万个名称?当然,它们不会全部放在屏幕上... ...)

如果您的客户稍后想要更改它,以便他们可以配置是显示 "Firstname Lastname"还是 "Lastname, Firstname."。使用 Format 选项,这很容易——只需要交换格式字符串。有了这个连接,你需要额外的代码。当然,在这个特定的例子中,这听起来不是什么大问题,但可以推断出来。

更好的测试方法是使用 Perfmon 和 CLR 内存计数器来观察内存。我的理解是,您使用 String 的全部原因。由于字符串是不可变的,因此格式化而不仅仅是串联字符串,这样会给垃圾收集器带来不必要的负担,需要在下一次传递中回收临时字符串。

StringBuilder 和 String.Format 虽然可能会慢一些,但是内存效率更高。

字符串连接有什么不好?

如果你正在处理一些需要易于阅读的东西(这是大多数代码) ,我会坚持使用操作符重载版本 UNLESS:

  • 代码需要执行数百万次
  • 你正在做的连接吨(超过4是一吨)
  • 该代码针对 CompactFramework

在其中至少两种情况下,我会使用 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);

在性能方面,我希望格式选项比 concat 慢,因为格式要求字符串为 解析。我不记得有必要优化这种指令,但如果我这样做了,我会看看 string方法,如 Concat()Join()

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

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

根据 MCSD 准备材料,微软建议在处理非常少量的串联(可能是2到4)时使用 + 操作符。我还是不知道为什么,但这是需要考虑的。

可怜那些可怜的翻译

如果你的 知道你的申请将留在英语,然后罚款,节省时间滴答作响。但是,许多区域性通常会在例如地址中看到 Lastname Firstname。

因此,使用 string.Format(),特别是如果你想让你的应用程序去任何英语不是第一语言的地方。

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

因为我不认为这里的答案涵盖了所有问题,所以我想在这里补充一点。

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 堆栈上)。如果您在堆栈上推送一个字符串(比如在 IL 中的字面值为 ldstr) ,那么您在堆栈上放置了一个对字符串指针的引用。

一旦调用 concat,这个引用就会成为一个问题,因为没有任何包含这两个字符串的字符串引用可用。这意味着。NET 需要分配一个新的内存块,然后用这两个字符串填充它。这个问题之所以存在,是因为分配成本相对较高。

这将问题改为: 如何减少 concat操作的数量?

所以,粗略的答案是: 对于 > 1 concat,string.Format,‘ +’对于1 concat 将工作得很好。如果您不关心进行微型性能优化,那么 string.Format在一般情况下会工作得很好。

文化笔记

还有一种叫做文化的东西。

string.Format允许您在格式设置中使用 CultureInfo。一个简单的操作符“ +”使用当前区域性。

如果您正在编写文件格式和 f.ex,这一点尤其重要。“添加”到字符串的 double值。在不同的计算机上,如果没有使用带有显式 CultureInfostring.Format,则可能会得到不同的字符串。

联邦快递。考虑一下,如果你改变一个’会发生什么。’当你写一个以逗号分隔的值文件时... 在荷兰语中,小数点是一个逗号,所以你的用户可能会得到一个“有趣的”惊喜。

更详细的答案

如果事先不知道字符串的确切大小,最好使用这样的策略来过度分配所使用的缓冲区。首先填充空闲空间,然后复制数据。

增长意味着分配一个新的内存块,并将旧的数据复制到新的缓冲区。然后可以释放旧的内存块。在这一点上你得到了底线: 发展是一个昂贵的操作。

最实际的方法是使用过度分配策略。最常见的策略是以2的幂为单位过度分配缓冲区。当然,您必须做得更聪明一些(因为如果您已经知道需要128个字符,那么从1、2、4、8增长是没有意义的) ,但是您已经了解了情况。该策略确保您不需要太多我上面描述的昂贵操作。

StringBuilder是一个基本上过分分配基础缓冲区的类,其幂次为2。string.Format在引擎盖下使用 StringBuilder

这使得您的决策成为过度分配和追加(- 多个)(w/w。区域性)或仅仅分配和追加之间的基本权衡。

从 C # 6.0 插值字符串开始就可以做到这一点,从而使格式更加简单。

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

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

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

关于字符串插值,请参阅 这篇 dotnetperls 的文章

如果您正在寻找格式化字符串的默认方法,那么这在可读性和性能方面是有意义的(除非微秒将对您的特定用例产生影响)。

从2015年8月19日开始的一周内,这个问题将正好出现7年。现在有一个更好的方法来做这件事。好多了在可维护性方面,因为我还没有做过任何性能测试,相比之下,只是连接字符串(但这些天它重要吗?几毫秒的差别?).使用 C # 6.0的新方法:

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

这个新特性是 好多了、 IMO 和 实际上对我们来说更好,因为我们有代码来构建查询字符串,其值取决于某些因素。设想一个查询字符串,其中有6个参数。所以不是做一个,例如:

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字符串插值特性:

Console.WriteLine($"{p.FirstName} {p.LastName}");

它的性能类似于使用“ +”。