System.String 有几十个方法。本页介绍在实际代码中承担大部分工作的那些方法,以及重要的选项和陷阱。有一条规则适用于所有方法:字符串是不可变的,所以每个方法都会返回一个新字符串(或一个值),原字符串保持不变。单独一行写 s.Trim(); 什么也不会做;要写成 s = s.Trim();。
Split
Split 在每个分隔符处把字符串拆成一个 string[]:
输出:
3
apple
banana
cherry
red/green/ blue/yellow
INFO/started/ready
注意 " blue":Split 正好在分隔符处切开,并保留所有空格。有两个选项可以处理常见的清理工作:
输出:
5
4
c#|dotnet|linq
key=[Subject] value=[Re: Meeting: Friday]
3
RemoveEmptyEntries 会去掉长度为零的部分,但不会去掉只包含空格的部分(上面的 " " 被保留了下来)。.NET 5 新增了 StringSplitOptions.TrimEntries,两者可以组合使用:tags.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) 一次调用就能得到干净的结果。把 null 作为分隔符传入会按任意空白字符分割,这是把文本拆成单词的常用方式。
按单个 char 分割(Split(','))不需要数组。接受一个 char 加选项,或者接受单个 string 的重载从 .NET Core 2.0 起才有;上面用到的数组形式在任何地方都能用,包括 .NET Framework 和 Unity。
Replace
Replace 会替换字符或子字符串的每一个匹配项:
输出:
5551234567
555.123.4567
Cats and dogs and CATS
dogs and dogs and dogs
dogs and cats and CATS
too many spaces
带 StringComparison 参数的重载在 .NET Core 2.0 及更高版本中可用。在 .NET Framework 中,不区分大小写的替换用 Regex.Replace(text, Regex.Escape(find), replacement, RegexOptions.IgnoreCase) 完成。替换为 "" 就是删除字符的方法,少量替换时链式调用几次 Replace 也完全没问题。
Contains、IndexOf、StartsWith 和 EndsWith
这些方法用于查找。Contains 回答是或否;IndexOf 和 LastIndexOf 返回位置,没有匹配时返回 -1;StartsWith 和 EndsWith 检查开头和结尾。
输出:
True
False
True
True
8
30
28
-1
16
False
True
True
它们默认都区分大小写。Contains(string, StringComparison) 在 .NET Core 2.1 及更高版本中才有;IndexOf(...) >= 0 的写法在所有版本中都可用,这就是你在旧代码里常看到它的原因。IndexOfAny(new[] { ',', ';' }) 查找几个字符中最先出现的那个。
关于区域性:不传 StringComparison 时,StartsWith、EndsWith、IndexOf(string) 和 CompareTo 使用区分区域性的规则,而 Contains、Replace 和 IndexOf(char) 按序号比较。显式传入 StringComparison.Ordinal 或 OrdinalIgnoreCase 可以消除歧义,代码分析器也推荐这样做。
Trim、TrimStart 和 TrimEnd
Trim 去掉两端的空白字符(空格、制表符、换行符)。TrimStart 和 TrimEnd 只去掉一端。如果传入字符,就改为去掉这些字符:
输出:
[ana@example.com]
[ana@example.com \n]
[ x]
SALE
7
path/to/dir
1,234.50
Trim 只处理两端;字符串中间的空格会保留。对 "000" 调用 TrimStart('0') 会返回空字符串,所以解析数字字符串时请用 int.Parse,而不是手动去掉前导零。
ToUpper 和 ToLower
输出:
MAYA LOPEZ
maya lopez
MAYA LOPEZ
Coffee
ToUpper 和 ToLower 遵循当前区域性,所以在土耳其语中,"i".ToUpper() 是带点的大写 İ,而不是 I。对于标识符、键和文件名,请使用 ToUpperInvariant 和 ToLowerInvariant。比较时,请使用 StringComparison,而不是先转换大小写(见 字符串)。要让每个单词首字母大写,可以用 CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text),不过它不会处理已经全部大写的单词。
PadLeft 和 PadRight
补齐会添加字符,直到字符串达到指定的总宽度。它在定宽输出和给编号补零时很方便:
输出:
000042
[Tea ]
[ Tea]
Too long already
Espresso.. 2.40
Latte..... 3.60
Mocha..... 3.95
插值的对齐写法({name,-10})可以在字符串内部完成同样的工作。
Join 和 Concat
string.Join 在任意集合的元素之间插入分隔符。string.Concat 直接连接,不加分隔符:
输出:
Ana, Ben, Chloe
1 + 2 + 3
True
2026-09
AnaBenChloe
Ana, Ben and Chloe
Join 是构建分隔列表的正确方式:不需要去掉末尾多余的分隔符,而且一次就能构建出结果。它会对每个元素调用 ToString(),所以数字和对象也同样适用。
Reverse、Insert 和 Remove
没有返回字符串的 string.Reverse 方法。常用的两种做法:
输出:
desserts
desserts
True
2026/Q3-09
Hello
HelloWorld
单独调用 word.Reverse() 返回的是 IEnumerable<char>,而不是字符串;打印它只会显示一个类型名。两种方法反转的都是 UTF-16 代码单元,这会破坏由两个单元组成的字符(大多数 emoji)以及带组合重音符号的字母。
速查表
| 任务 | 方法 |
|---|---|
| 拆分成多个部分 | s.Split(',')、s.Split(new[] {',', ';'}, StringSplitOptions.RemoveEmptyEntries) |
| 全部替换 | s.Replace("a", "b") |
| 是否包含 | s.Contains("x")、s.Contains("x", StringComparison.OrdinalIgnoreCase) |
| 查找位置 | s.IndexOf("x")、s.LastIndexOf('x')(不存在时为 -1) |
| 是否以某内容开头或结尾 | s.StartsWith("x")、s.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase) |
| 去掉两端空白 | s.Trim()、s.TrimStart()、s.TrimEnd() |
| 转换大小写 | s.ToUpper()、s.ToLowerInvariant() |
| 补齐到指定宽度 | s.PadLeft(6, '0')、s.PadRight(10) |
| 连接集合 | string.Join(", ", items) |
| 取字符串的一部分 | s.Substring(start, length) |
| 插入或删除 | s.Insert(i, "x")、s.Remove(i, count) |
| 检查 null 或空白 | string.IsNullOrWhiteSpace(s) |
常见问题
C# 中如何分割字符串?
text.Split(',') 返回一个 string[],包含逗号之间的各个部分。多个分隔符用数组传入(Split(new[] { ',', ';' })),字符串分隔符用 Split(new[] { ", " }, StringSplitOptions.None),StringSplitOptions.RemoveEmptyEntries 可以去掉连续分隔符产生的空部分。.NET 5 新增了 StringSplitOptions.TrimEntries,用来去掉每个部分两端的空白。
如何在忽略大小写的情况下判断一个字符串是否包含另一个字符串?
在 .NET Core 2.1 及更高版本中:text.Contains("error", StringComparison.OrdinalIgnoreCase)。.NET Framework 没有这个重载,请用 text.IndexOf("error", StringComparison.OrdinalIgnoreCase) >= 0。避免使用 text.ToLower().Contains(...),它会分配一份副本,而且在某些区域性下可能出错。
C# 中 String.Replace 会修改原字符串吗?
不会。字符串是不可变的,所以 Replace 返回一个替换了所有匹配项的新字符串,原字符串保持不变。要写成 s = s.Replace("old", "new");。它替换的是所有匹配项,而不只是第一个;如果只想替换第一个,可以用 IndexOf 找到它再重新拼接字符串,或者在 Regex 实例上调用 Replace 并把次数设为 1。
字符串没找到时 IndexOf 返回什么?
-1。任何大于等于 0 的结果都是第一个匹配项从零开始的位置。在把结果当作索引使用之前,一定要先检查 -1,因为 Substring(-1) 会抛出异常,而 Substring(-1 + 1) 会悄悄返回整个字符串。LastIndexOf 的用法相同,只是从末尾开始查找。
C# 中如何反转字符串?
把它复制到 char 数组里,反转数组,再构造一个新字符串:char[] a = s.ToCharArray(); Array.Reverse(a); string r = new string(a);。用 LINQ 的话,new string(s.Reverse().ToArray()) 效果相同。两者反转的都是 UTF-16 单元,所以 emoji 和组合重音符号可能会被打乱。