C# 字符串

Tip

本文是已了解至少一种编程语言并正在学习 C# 的开发人员的 “基础知识 ”部分的一部分。 如果你不熟悉编程,请先 学习入门 教程。

来自 Java 或 C++? C# string 是一种不可变的引用类型,由 String 提供支持。 UTF-16 是内存中编码,类似于Java的 String。 与 C/C++ 不同,字符串不会以 null 结尾,不会衰减到指针中。

字符串是字符序列。 在 C# 中,stringSystem.String 类型的语言关键字。 写入的每个字符串文本都会生成一个 System.String 实例。

stringString

关键字 stringString 类型名称引用同一类型。 它们编译成相同的中间语言(IL)。

// The 'string' keyword is an alias for System.String. The two are identical.
string a = "hello";
String b = "hello";

Console.WriteLine(a == b);                // True
Console.WriteLine(typeof(string) == typeof(String)); // True

在你自己的代码中,优先使用 string 关键字。 它与其他内置类型关键字(intbooldouble)一致,并且无需 using System; 指令即可正常工作。

字符串是不可变的

不可变 意味着创建该值后无法更改。 创建 string 后,您将无法更改其中的字符。 方法(例如 ToUpperInvariantReplaceSubstringTrim 返回包含修改值 的新 字符串。 原始实例保持不变。

string greeting = "hello";

// ToUpper returns a *new* string. The original is unchanged.
string shouted = greeting.ToUpperInvariant();

Console.WriteLine(greeting);  // hello
Console.WriteLine(shouted);   // HELLO

由于字符串是不可变的,因此可以在方法和线程之间安全地共享它们。 这种不可变性解释了为什么 string 该类型的行为类似于日常使用中的值类型,即使它是引用类型。

当你在循环中由许多小片段构建字符串时,请使用 StringBuilder 进行原地追加,并在最后生成一个字符串:

// For many sequential edits, StringBuilder avoids allocating a new string each time.
var builder = new StringBuilder();
for (int i = 1; i <= 3; i++)
{
    builder.Append("item ").Append(i).Append(';');
}
string result = builder.ToString();
Console.WriteLine(result);    // item 1;item 2;item 3;

字符串文本

C# 提供四种文本形式。 不同形式适合不同的内容。 作为快速指南:

  • 对于简短且简单、最多包含几个转义序列的文本,请使用 常规字面量
  • 当反斜杠主宰内容(如Windows路径或正则表达式模式)时,请使用 verbatim 文本
  • 原始字符串文本 用于多行文本或结构化格式文本,例如内联 JSON、SQL、XML 或格式化消息块。
  • 当需要嵌入值时,给上述任意字面量添加 $ 前缀即可得到插值字符串
  • 向字面量添加后缀 u8,以生成 UTF-8 字节序列(即 byteReadOnlySpan<T>),供面向字节的 API 使用。 有关详细信息,请参阅语言参考中的 UTF-8 字符串文本

常规文本和转义序列

正则字符串文本括在双引号中。 反斜杠启动转义序列:

// Common escape sequences inside a regular string literal.
string tabbed   = "name:\tAda";          // \t  tab
string twoLines = "line 1\nline 2";      // \n  newline
string quoted   = "She said \"hi\".";    // \"  literal quote
string path     = "C:\\src\\app";        // \\  literal backslash

Console.WriteLine(tabbed);
Console.WriteLine(twoLines);
Console.WriteLine(quoted);
Console.WriteLine(path);

常见的转义序列包括 \n (换行符)、 \t (制表符)、 \" (文本引号)、 \\ (文本反斜杠)、 \0 (null char)和 Unicode 转义(\uXXXX\UXXXXXXXX)。

从 C# 13 开始, \e 表示 ESC 控件字符(U+001B)。 它是 ANSI 终端转义序列的起始字节:

// Beginning in C# 13, \e represents the ESC control character (U+001B).
// It's used to start ANSI terminal escape sequences.
string esc = "\e[31mError\e[0m: file missing";

Console.WriteLine(esc);                  // ESC[31mError ESC[0m: file missing
Console.WriteLine((int)'\e');          // 27

当文本短且只包含少量转义序列时,请使用常规文本。 转义开始超过可见字符的数量后,切换到逐字或原始文本。

逐字文本

逐字文字以 @前缀 。 反斜杠按字面值处理,这对于 Windows 路径和正则表达式模式非常有用:

// A verbatim string literal (@) treats backslashes literally.
// Useful for Windows paths and regular expressions.
string winPath = @"C:\src\app\readme.md";
string pattern = @"\d{3}-\d{4}";

Console.WriteLine(winPath);
Console.WriteLine(pattern);

若要在逐字字符串中嵌入文本引号,请将其加倍: @"She said ""hi""." 逐字字符串还可以跨越多个物理行。

反斜杠是内容的一部分,但没有许多嵌入的引号时,逐字文本是正确的选择。 对于带引号的多行文本或内容,原始字符串文本通常更清晰。

原始字符串字面量

对于包含引号、反斜杠或多行的任何文本,首选 原始字符串文本。 它们完全消除了转义噪声,因此尤其适用于内联 JSON、SQL、XML、正则表达式模式以及格式化文本块,在这些场景中,源文本应看起来与输出结果一致:

// Raw string literals use three or more quotes and need no escaping.
// The source looks like the output, which is ideal for inline JSON, SQL, XML, and the like.
string json = """
    {
        "name": "Ada",
        "roles": ["admin", "editor"]
    }
    """;

string sql = """
    SELECT Id, Name
    FROM   Users
    WHERE  Name = 'O''Brien'
    """;

Console.WriteLine(json);
Console.WriteLine(sql);

原始字符串文本除消除转义序列,还能容纳所需的任何格式和引用。 有关完整规则,请参阅 原始字符串文本

内插字符串

$前缀将文本转换为内插字符串。 对孔中的 {} 表达式进行求值及其插入结果,并且可以在孔内应用标准格式说明符和对齐方式。 插值还可以与其他字面量形式结合使用——使用 $@"..." 对逐字字面量进行插值,或使用 $"""...""" 对原始字符串字面量进行插值,以获得丰富格式的输出:

// The $ prefix evaluates expressions inside { } and inserts their values.
string name = "Ada";
int score = 92;

string greeting = $"Hello, {name}! Your score is {score}.";
// Format specifiers and alignment work inside the holes.
string formatted = $"pi = {Math.PI:F3}, padded = |{name,10}|";

// Combine $ and """ for richly formatted multiline output.
string report = $"""
    Report for {name}
    -----------------
    Score : {score}
    Grade : {(score >= 90 ? "A" : "B")}
    """;

Console.WriteLine(greeting);
Console.WriteLine(formatted);
Console.WriteLine(report);

建议使用内插方法从日常代码中的值编写字符串。

索引和 char

A string 是 UTF-16 代码单元序列。 索引器返回一个 System.Char,表示单个 UTF-16 代码单元,不一定是完整的 Unicode 代码点。 Length 返回代码单元计数。

// A string is a sequence of UTF-16 code units. s[i] returns one char.
string word = "café";

Console.WriteLine(word.Length);          // 4
Console.WriteLine(word[0]);              // c

foreach (char c in word)
{
    Console.Write($"{c} ");              // c a f é
}
Console.WriteLine();

对于可能包含表情符号或基本多语言平面之外字符的文本,请使用 Runerune 遍历,或使用 StringInfo 按字素簇遍历。 简单 char 迭代适用于大多数西文文本和以 ASCII 为主的内容。

字符串相等性

string 进行相等性比较时,比较的是字符序列,而不是引用:

// String equality compares character sequences, not references.
string left  = "hello";
string right = string.Concat("hel", "lo");

Console.WriteLine(left == right);                                      // True
Console.WriteLine(left.Equals(right, StringComparison.Ordinal));       // True

对于需要区分区域设置或区分大小写的比较,请传递显式 StringComparison 值。 对协议值、标识符和其他非语言文本使用 StringComparison.Ordinal

常见字符串操作

使用下表作为 C# 中日常字符串操作的快速指南:

类别 它涵盖的内容
搜寻 查找字符或子字符串、测试前缀和后缀
拆分 将字符串分解为分隔符上的子字符串
连接 合并字符串 — +、插值、ConcatJoin
修改 生成转换后的副本 — ReplaceTrimSubstring
比较 使用合适的 StringComparison 测试相等性和排序

String 参考文档中详细说明了完整的 API 全貌——每个重载、每个方法。

另见