C# 字符串拼接:+、String.Format 与 $
2021 年 7 月 30 日 · 1 分钟读完
三种常见写法拼出同一个字符串 "dp" is 18,用 ILDASM 看看编译器把它们变成了什么。ILDASM 的用法见 读懂 .NET IL 代码。
一、+ 号
int age = 18;
string prefix = "\"dp\" is ";
string test1 = prefix + age;
翻一下 String 类的定义,会发现它只重载了 == 和 !=,并没有重载 +:
public sealed class String : IComparable, ICloneable, IConvertible, IComparable<String>, IEnumerable<char>, IEquatable<String>
{
public static bool operator ==(String a, String b);
public static bool operator !=(String a, String b);
}
那 + 是怎么工作的?看 IL:
.locals init ([0] int32 age,
[1] string Prefix,
[2] string test1)
IL_0001: ldc.i4.s 18
IL_0003: stloc.0
IL_0004: ldstr "\"dp\" is "
IL_0009: stloc.1
IL_000a: ldloc.1
IL_000b: ldloc.0
IL_000c: box [mscorlib]System.Int32
IL_0011: call string [mscorlib]System.String::Concat(object, object) // 关键
IL_0016: stloc.2
字符串的 + 是编译器的特殊处理,直接翻译成 String.Concat。注意 box:int 被装箱成了 object。
二、String.Format
string test2 = string.Format("{0}{1}", prefix, age);
三、$ 插值字符串
string test3 = $"{prefix}{age}";
IL:
IL_000a: ldstr "{0}{1}"
IL_000f: ldloc.1
IL_0010: ldloc.0
IL_0011: box [mscorlib]System.Int32
IL_0016: call string [mscorlib]System.String::Format(string, object, object) // 关键
在当时的编译器里,$ 只是 String.Format 的语法糖。
运行结果

延伸
- C# 10 之后
$不再是String.Format:.NET 6 引入了插值字符串处理器(DefaultInterpolatedStringHandler),$"{prefix}{age}"会编译成往一个栈上缓冲区里依次追加,不再装箱 int,也不用解析{0}格式串,速度和内存都明显好于String.Format。同样的代码,换个编译器就变快了,这也是看 IL 的意义。 - 全是字符串时,编译器会把
a + b + c合并成一次String.Concat(a, b, c),不会产生中间字符串,放心用+。 - 循环里拼接用
StringBuilder:字符串不可变,循环里s += x每次都会新建一个字符串,n 次就是 O(n²) 的复制。拼日志、拼 CSV、拼报文时用StringBuilder,或者string.Join。 - 格式化写法都能用在
$里:$"{value:F2}"保留两位小数,$"{dt:yyyy-MM-dd HH:mm:ss}"格式化时间,$"{code:X4}"十六进制补齐 4 位,$"{name,-10}"左对齐占 10 格。 - C# 11 的原始字符串:
"""包起来的字符串不用转义引号和反斜杠,拼 JSON 或 SQL 很方便;配合$$"""{ "id": {{id}} }"""还能插值。
原文首发于 blog.csdn.net