← 全部文章 C# 语言

C# 字符串拼接:+、String.Format 与 $

2021 年 7 月 30 日 · 1 分钟读完

目录
  1. 一、+ 号
  2. 二、String.Format
  3. 三、$ 插值字符串
  4. 运行结果
  5. 延伸

三种常见写法拼出同一个字符串 "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}} }""" 还能插值。

# C# # 字符串 # IL

原文首发于 blog.csdn.net