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.Ordinal 或 OrdinalIgnoreCase——比 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);
文件 / 网络 / 数据库读取永远显式指定编码——别依赖默认。
→ 下一篇 控制流