Span切片越界会直接崩溃而非抛异常,因其零开销安全模型要求开发者自行保证索引合法;字符串转Span必须用AsSpan(),不可用fixed或stackalloc;处理UTF-8需转字节视图;传参时ref/readonly影响编译与生命周期。

Span 切片数组时,索引越界会直接崩溃,不是抛异常
用 Span<t>.Slice()</t> 对数组切片,如果起始索引或长度超了原数组边界,运行时直接触发 System.IndexOutOfRangeException —— 而且这个异常无法被常规 try/catch 捕获(在某些 AOT 或 unsafe 上下文中甚至可能表现为访问违规)。这不是设计缺陷,而是 Span 的零开销安全模型决定的:它不带运行时边界检查开销,但要求你**自己保证合法范围**。
实操建议:
- 永远先用
array.Length或span.Length校验参数,再调Slice();别依赖“反正会抛异常”来兜底 - 避免从用户输入、网络数据、配置项里直接读取切片起始/长度——这些必须做显式校验
- 调试时可用
Debug.Assert(span.Length >= start + length)辅助定位问题点 - 对已知安全的内部计算(如循环分块),可考虑用
Unsafe.Add(ref array[0], offset)+MemoryMarshal.CreateSpan()绕过 Slice,但仅限极少数性能敏感路径
字符串转 Span 必须用 AsSpan(),不能用 stackalloc 或指针硬转
string 是不可变引用类型,底层字符存储在托管堆上。想拿到它的 Span<char></char>,唯一安全、标准的方式是调 str.AsSpan()。试图用 stackalloc char[100] 拷贝内容,或用 fixed (char* p = str) 再转 Span,不仅多一次拷贝、失去零拷贝意义,还可能因字符串驻留或 GC 移动导致悬垂指针。
常见错误现象:
- 用
fixed后没及时 pin 住字符串,在 span 生命周期内发生 GC → 随机乱码或崩溃 - 把
AsSpan()结果存到字段或跨 async 方法传递 → 编译不过(Span 不能作为类成员或 async 状态机字段) - 误以为
"abc".AsSpan()返回的是栈内存 → 实际它只是包装了字符串的堆地址,生命周期仍受字符串本身约束
正确姿势就是:用完即弃,只在单个同步方法内流转。
Span 处理 UTF-8 字符串需小心编码边界,AsSpan() 不等于字节切片自由
对 string 调 AsSpan() 得到的是 Span<char></char>,不是字节视图。若想按 UTF-8 字节切片(比如解析 HTTP header、二进制协议),得先转 ReadOnlySpan<byte></byte>,而 .NET 没有内置无分配的 string → UTF8 byte span 转换 —— 你得用 Encoding.UTF8.GetBytes()(分配)或 Encoding.UTF8.GetEncoder().Convert()(复杂),或者更推荐:Utf8String(.NET 6+)或 ReadOnlySpan<byte>.EncodeUtf8()</byte>(.NET 7+)。
使用场景与坑:
- 处理 HTTP/2 帧、Protobuf wire format、自定义二进制协议时,必须操作字节,
Span<char></char>会错位(一个中文占 3 字节,但char是 UTF-16) - 用
Encoding.UTF8.GetBytes(str)会分配新数组,破坏零拷贝目标;应优先尝试stackalloc byte[256]+Encoding.UTF8.GetBytes(str, stackBuffer),但要 catchArgumentException(缓冲区不够)并 fallback - .NET 7 的
str.AsSpan().EncodeUtf8(stackBuffer)是真零分配,但返回的是实际写入长度,不是 bool,别漏判截断
Span 传参时 ref 和 readonly 的区别直接影响能否编译通过
Span 是 ref-like 类型,函数参数声明方式决定它能不能接收变量、常量、临时值。最常踩的坑是:把需要 ref Span<byte></byte> 的 API(如 Base64.DecodeFromUtf8())传了 span 变量而不是 ref span,编译器直接报错 CS8350 —— 这不是警告,是硬性限制。
关键规则:
-
void M(Span<byte> s)</byte>:可接收arr.AsSpan()、stackalloc结果等临时 span,但函数内不能让 span 逃逸出作用域 -
void M(ref Span<byte> s)</byte>:必须传变量名加ref,函数可修改 span 的引用目标(比如重定向到另一块内存) -
void M(ReadOnlySpan<char> s)</char>:最常用,接收 string.AsSpan()、"abc".AsSpan()、数组切片都 OK,且允许跨 await 边界(因为它是 readonly) - 别给 Span 参数加
in—— 编译不通过;也别试图把 Span 存进 List 或 Dictionary —— 类型系统禁止
Span 的生命周期绑定太紧,稍微绕一下(比如想缓存某个切片结果供多次使用),就得换 Memory<t></t> 或老老实实拷贝。


















