string 是不可变的

string s = "hello";
s.ToUpper();        // 返回 "HELLO",s 没变
Console.WriteLine(s);    // hello

s = s.ToUpper();    // 显式赋值才生效

每个 String 操作返回新 string——原 string 永远不动。

字符串字面量

string normal = "hello\n\"quoted\"";        // 转义
string @verbatim = @"C:\path\no\escape";    // 逐字字符串,\ 不转义
string @multiline = @"line 1
line 2";

// raw string(C# 11+)——最现代
string raw = """
    多行
    "引号" 不用转义
    {} 也不用转义
    """;

raw string 用三个或更多 """,结尾的 """ 缩进决定基线(基线被自动去掉)。

插值字符串 $""

string name = "Alice";
int age = 30;

Console.WriteLine($"Hello {name}, age {age}");
Console.WriteLine($"{name} is {age} years old.");

// 表达式
Console.WriteLine($"sum = {1 + 2}");

// 格式
Console.WriteLine($"pi = {Math.PI:F2}");           // 3.14
Console.WriteLine($"hex = {255:X}");                // FF
Console.WriteLine($"date = {DateTime.Now:yyyy-MM-dd}");

// 对齐(负数左对齐)
Console.WriteLine($"|{name,10}|{age,-5}|");

$@""@$"" 同时插值 + 逐字(C# 8+)。

raw + 插值(C# 11+):

var json = $$"""
    {
        "name": "{{name}}",
        "age": {{age}}
    }
    """;

$${{ }} 包占位,避免和 { 字面冲突。

string.Format

老风格,仍可用:

string s = string.Format("Hello {0}, you are {1}.", name, age);

新代码用 $"" 插值。

拼接

// 少量拼接:+ 或 $""
string s = "Hello, " + name;
string s2 = $"{name}-{age}";

// 大量拼接:StringBuilder
var sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
    sb.Append(i).Append(",");
}
string result = sb.ToString();

// 已知元素的拼接:string.Join
string csv = string.Join(", ", new[] { "a", "b", "c" });   // "a, b, c"

为什么大循环要 StringBuilder?因为 string 不可变——s += x 每次创建新对象,O(n²) 慢。

比较

string a = "hello";
string b = "HELLO";

a == b                                         // false
a.Equals(b)                                    // false
a.Equals(b, StringComparison.OrdinalIgnoreCase) // true(不区分大小写)
a.CompareTo(b)                                 // 字典序

// 区域无关 vs 区域相关
string.Compare("a", "b", StringComparison.Ordinal)              // 字节比较,最快
string.Compare("a", "b", StringComparison.CurrentCulture)        // 受当前区域影响

99% 用 StringComparison.OrdinalOrdinalIgnoreCase——比 culture-sensitive 快、且更不易出意外(土耳其 i / 立陶宛 t 等会咬人)。

常用方法

"hello".ToUpper();              // "HELLO"
"hello".ToLower();
"  hi  ".Trim();                // "hi"
"hello".Substring(1, 3);        // "ell"
"hello".Replace("l", "L");      // "heLLo"
"a,b,c".Split(',');             // ["a", "b", "c"]
"hello".Contains("ll");         // true
"hello".StartsWith("he");       // true
"hello".IndexOf("l");           // 2
"hello".Length;                 // 5
"  ".IsNullOrWhiteSpace;        // ❌ 这是静态方法
string.IsNullOrWhiteSpace("  ");// true
string.IsNullOrEmpty("");       // true

字符(char)

char c = 'A';
char.IsDigit('5');          // true
char.IsLetter('A');         // true
char.IsWhiteSpace(' ');     // true
char.ToUpper('a');          // 'A'
(int)'A';                   // 65(强转 int 拿 ASCII / Unicode 码点)

Span 高性能场景

ReadOnlySpan<char> span = "hello".AsSpan();
ReadOnlySpan<char> sub = span.Slice(1, 3);    // "ell",零分配

Span<T> 提供子串视图而不复制——int.TryParse(span, out var n) 等 API 也接受 span。性能敏感的解析 / 拆分用这个。

字符串 + null

string? s = null;
s.Length;          // ❌ NullReferenceException

s?.Length;         // null(短路)
s ?? "default";    // s 为 null 时用 "default"

C# 8+ 的可空引用类型(第 18 篇)会编译器警告——务必启用。

编码 / 二进制

byte[] bytes = Encoding.UTF8.GetBytes("hello");
string back = Encoding.UTF8.GetString(bytes);

文件 / 网络 / 数据库读取永远显式指定编码——别依赖默认。

→ 下一篇 控制流