ReadOnlySpan 是字符串切片唯一零分配方案,因 .AsSpan() 直接指向底层字符数组,而 Memory 无法隐式转换且强制包装会触发堆分配。

直接用 ReadOnlySpan<char></char> 替代 string.Substring(),是 C# 字符串处理中唯一能真正避免堆分配的路径;Memory<char></char> 在字符串场景几乎无用——它不支持从 string 隐式转换,且包装字符串会引入额外开销,纯属误用。
为什么 ReadOnlySpan 是字符串切片的唯一合理选择
string 是只读的托管对象,.NET 为它专门设计了零成本视图类型:ReadOnlySpan<char></char>。它能通过 .AsSpan() 直接获得指向字符串底层字符数组的指针+长度,不复制、不分配、不触发 GC。
- 错误写法:
var part = input.Substring(5, 3)→ 每次都 new string,高频调用下 GC 压力陡增 - 正确写法:
var part = input.AsSpan().Slice(5, 3)→ 纯偏移计算,O(1) -
ReadOnlySpan<char></char>支持所有常用操作:IndexOf、Trim、Split(返回ReadOnlySpan<char>[]</char>)、StartsWith,全部零分配 - 注意:不能赋值给字段或异步方法参数——它不是用来“持有”的,而是“即用即弃”的解析工具
Memory 在字符串处理中基本不该出现
Memory<char></char> 无法从 string 构造(没有 .AsMemory() 扩展),强行包装需先转成 char[],这反而触发堆分配,完全违背初衷。
- 编译报错:
string s = "abc"; Memory<char> m = s;</char>→ CS0029 无法隐式转换 - 绕路写法:
Memory<char> m = s.ToCharArray().AsMemory()</char>→ 先分配数组再包装,两倍开销 - 即使你真有长期持有需求(比如缓存某段文本),也该用原始
string或ReadOnlyMemory<char></char>+ 自定义IMemoryOwner<char></char>,而非硬套Memory<char></char> - 唯一可能用到的边缘场景:配合
Utf8Parser.TryParse解析字符串中的数字时,输入仍必须是ReadOnlySpan<char></char>,Memory<char></char>仍需先取.Span—— 多此一举
常见字符串解析任务的 Span 写法对照
把传统 string 操作换成 ReadOnlySpan<char></char>,只需改两处:入口加 .AsSpan(),后续所有切片/查找都用 Span 方法。
- CSV 行拆分:
ReadOnlySpan<char> line = "a,b,c".AsSpan(); int pos = line.IndexOf(','); ReadOnlySpan<char> first = line.Slice(0, pos);</char></char> - HTTP 头解析:
ReadOnlySpan<char> header = requestLine.AsSpan(); int sep = header.IndexOf(':'); var key = header.Slice(0, sep).Trim(); var value = header.Slice(sep + 1).Trim();</char> - JSON 键提取:
ReadOnlySpan<char> json = "{\"name\":\"Alice\"}".AsSpan(); int start = json.IndexOf('"') + 1; int end = json.IndexOf('"', start); var name = json.Slice(start, end - start);</char> - 避免陷阱:不要在循环里反复调用
input.AsSpan()—— 提取一次复用即可,虽然开销极小,但语义更清晰
容易被忽略的边界问题:null 和空字符串
string.AsSpan() 对 null 抛出 ArgumentNullException,对空字符串返回长度为 0 的 ReadOnlySpan<char></char>,这点和传统 API 不一致,必须显式检查。
- 安全写法:
if (input is null) return; var span = input.AsSpan(); - 不要依赖
span.Length == 0来判断 null —— 它只代表空串,不覆盖 null 场景 - 若上游数据可能为 null,优先用
input?.AsSpan() ?? ReadOnlySpan<char>.Empty</char> - 所有
Span方法(如IndexOf)在空 span 上行为确定(返回 -1),但越界切片(Slice(0, 10)在长度为 0 的 span 上)会抛ArgumentException,需提前校验


















