C# 中的 string 是一个 UTF-16 字符(char 值)序列。它是引用类型,但在关键方面表现得像值:它是不可变的,而且 == 比较的是文本,而不是对象是否为同一个。string 是 .NET 类型 System.String 的 C# 关键字,两者可以互换。
输出:
Porto
5
0
*****
hi
Porto has 5 letters
字符串是不可变的
没有任何方法会就地修改字符串。ToUpper、Replace、Trim 以及其他所有方法都返回一个新字符串,原字符串保持不变。忘记使用返回值是非常常见的字符串 bug:
输出:
[ maya ]
[MAYA]
tea
tea and cake
不可变性让字符串可以安全地在线程之间共享,也可以安全地用作字典的键。代价是在循环中一段一段地构建字符串时,每一步都会创建一个新字符串;这种情况请使用 StringBuilder。
长度与索引
Length 是一个属性(不加括号)。索引器 s[i] 返回位置 i 上的 char,从 0 开始计数,并且是只读的:
输出:
7
A
1
IndexOutOfRangeException
XB-2931
C# 8 及更高版本还接受从末尾开始的索引:code[^1] 是最后一个字符,等同于 code[code.Length - 1]。要一次取多个字符,请看 子字符串。
Length 统计的是 UTF-16 代码单元,而不是读者眼中的字符个数。大多数语言中的大多数字母都是一个单元,但 emoji 和一些少见的文字使用两个单元(一个代理对),所以 "👍".Length 是 2。
比较字符串
== 和 != 比较字符,区分大小写。其他情况请传入一个 StringComparison 值来说明如何比较:
输出:
False
False
True
True
apple first
True
False
False
True
该用哪种比较:
Ordinal比较字符的数字编码。它快速、可预测,并且在每台机器上结果都相同。用于标识符、文件名、键、协议值以及任何不展示给人看的内容。OrdinalIgnoreCase与上面相同,但忽略大小写。适合不区分大小写的检查,比如文件扩展名或 HTTP 头。CurrentCulture(以及CurrentCultureIgnoreCase)遵循用户语言的规则,因此排序方式与该语言的词典一致。用于对展示给用户的列表排序。InvariantCulture使用区分区域性、但不依赖用户语言设置的规则。它仍然取决于全球化库:现代 .NET 使用 ICU,.NET Framework 使用 Windows 的 NLS,两者对某些字符串的排序可能不同。
不带 StringComparison 的 string.Compare(a, b) 和 a.CompareTo(b) 使用当前区域性,因此结果会因机器而异。避免用 a.ToLower() == b.ToLower() 来比较:它会分配两个新字符串,而且在某些区域性下会给出错误答案(土耳其语中带点和不带点的 i 是经典例子)。
当 a 为 null 时调用 a.Equals(b) 会抛出 NullReferenceException,而 a == b 和静态的 string.Equals(a, b) 可以安全地处理 null。
转义字符
在普通字符串字面量中,反斜杠表示转义序列的开始:
| 转义 | 含义 |
|---|---|
\n | 换行(line feed) |
\r | 回车 |
\t | 制表符 |
\\ | 反斜杠 |
\" | 双引号 |
\' | 单引号(只在 char 字面量中需要) |
\0 | 空字符 |
\uXXXX | 按 4 位十六进制代码表示的 UTF-16 字符,例如 \u00e9 |
\U0001F44D | 按 8 位十六进制代码表示的字符;超过 U+FFFF 时会变成两个 char 值 |
\xH 至 \xHHHH | 按 1 至 4 位十六进制数字表示的字符(避免使用:长度有歧义) |
\a、\b、\f、\v | 响铃、退格、换页、垂直制表符 |
\e | 转义符 U+001B(C# 13) |
输出:
Name: Ana
City: Porto
She said "hi"
C:\temp\report.txt
cafe = cafe
' is a quote char
无法识别的序列如 "\d" 会导致编译错误(CS1009,无法识别的转义序列),把 Windows 路径或正则表达式写成普通字符串时就会遇到这种情况。
逐字字符串与多行字符串
在字面量前加 @ 会让它成为逐字字符串:反斜杠是字面字符,字符串可以跨多行。唯一剩下的转义是 "",它表示一个双引号。
输出:
C:\Users\ana\Documents\notes.txt
\d{3}-\d{4}
He said "ready" and left.
Rua das Flores 12
4050-262 Porto
Portugal
first line
second line
逐字多行字符串包含引号之间的所有内容,包括后续行的缩进,所以它们通常像上面那样顶格书写。其中的换行符就是源文件中的换行符,所以用 Windows 换行符保存的文件会得到 \r\n。
C# 11 新增了原始字符串字面量,同时解决了这两个问题。它们以三个或更多双引号开始和结束,完全不需要转义,并且会去掉与结束引号相同的缩进:
// C# 11 and later
string json = """
{
"name": "Ana",
"path": "C:\temp"
}
""";
// The 4 spaces before the closing """ are removed from every line.
如果内容本身包含 """,就改用四个引号开始和结束。原始字面量也可以与插值结合($"""...""");见 字符串插值。
null、空字符串与空白
字符串变量可以是 null(根本没有字符串)、空字符串("",长度为 0),或者只包含空白字符。有两个静态辅助方法可以检查这些情况,而不会有 NullReferenceException 的风险:
输出:
null empty: True blank: True
"" empty: True blank: True
" " empty: False blank: True
"Ana" empty: False blank: False
True
验证用户输入时,你几乎总是需要 string.IsNullOrWhiteSpace。string.Empty 和 "" 是同一个值,写哪个只是风格问题。
遍历字符
字符串是可枚举的,所以 foreach 会访问每一个 char。char 类型有用来对字符分类的静态方法:
输出:
letters=6 digits=4 upper=1 symbols=1
!6202tesnuS
连接字符串
+ 用于拼接,另一边可以是任意类型(它会调用 ToString())。要组合很多值,请优先使用插值或 string.Join:
输出:
Ana Silva, 31
Ana Silva, 31
AnaSilva
tea | coffee | juice
Total: 23
Total: 5
最后两行展示了一个优先级陷阱:+ 从左到右求值,所以一旦左边是字符串,后面的每个 + 都是拼接。在拼接中进行算术运算时,请加上括号。
常见问题
C# 中如何获取字符串的长度?
使用 Length 属性:"hello".Length 是 5。它是属性,不是方法,所以不加括号。Length 统计的是 UTF-16 代码单元,所以 emoji 或其他基本多文种平面之外的字符算作 2。对 null 字符串访问它会抛出 NullReferenceException。
C# 中如何比较字符串?
a == b 比较文本内容,并且区分大小写。不区分大小写的相等比较请用 string.Equals(a, b, StringComparison.OrdinalIgnoreCase)。要对字符串排序,请用 string.Compare(a, b, StringComparison.Ordinal) 或区分区域性的比较,它返回负数、零或正数。
C# 中如何写多行字符串?
在字面量前加 @ 使其成为逐字字符串:它可以跨多行,反斜杠按字面处理,双引号写作 ""。在 C# 11 及更高版本中,用 """ 包围的原始字符串字面量也可以跨多行,会去掉公共缩进,而且完全不需要转义。你也可以用 \n 或 Environment.NewLine 把多行连接起来。
C# 中的逐字字符串是什么?
以 @ 开头的字符串字面量,例如 @"C:\Users\ana"。其中的转义序列不会被处理,所以反斜杠就是反斜杠,这让它成为 Windows 路径和正则表达式的自然选择。它里面唯一的转义是用 "" 表示一个双引号。
C# 中 string 和 String 有什么区别?
没有区别。string 是 .NET 类型 System.String 的 C# 关键字别名,就像 int 是 System.Int32 的别名一样。惯例是声明时用 string,静态调用时写 string.IsNullOrEmpty(...),但两者编译出来完全相同。
C# 支持哪些转义字符?
\n 换行,\r 回车,\t 制表符,\\ 反斜杠,\" 双引号,\' 单引号,\0 空字符,\uXXXX 按十六进制代码表示的 UTF-16 字符,\U0001F600 按 8 位代码表示的字符(它会变成一个代理对),\a 响铃,\b 退格,\f 换页,\v 垂直制表符,以及 \e(C# 13)转义符。未知的序列如 \d 会导致编译错误(CS1009),这就是正则表达式通常写成逐字字符串的原因。