这是一个非常关键的问题,掌握好 Span<byte>、ReadOnlySpan<byte> 和 byte[] 的区别,对我们优化程序的性能非常重要。
下面从定义、差异、使用场景、注意点逐一解释:
✅ 一、概念对比
| 类型 |
描述 |
是否可修改 |
是否分配堆内存 |
byte[] |
实际存储数据的托管数组 |
✅ 是 |
✅ 是(会触发 GC) |
Span<byte> |
指向连续内存的“可写切片视图”,不分配内存 |
✅ 是 |
❌ 否(栈或固定内存) |
ReadOnlySpan<byte> |
Span 的只读版本,不能修改数据 |
❌ 否 |
❌ 否(安全只读) |
✅ 二、用途场景
| 场景 |
推荐使用 |
| 原始数据接收/缓冲区 |
byte[](原始载体) |
| 频繁读取和处理、切片解析 |
Span<byte>(高性能,零分配) |
| 只读解析、不需要写入 |
ReadOnlySpan<byte>(安全性更好) |
| 与老 API 或网络 IO 打交道 |
byte[](因为 Span<T> 不能跨线程/不能 async 保存) |
✅ 三、典型使用示例
✅ byte[] 作为原始数据载体
byte[] raw = new byte[1024];
raw[0] = 0x01;
✅ 使用 Span<byte> 提高解析性能(零拷贝)
Span<byte> span = raw.AsSpan(offset, length);
short value = BitConverter.ToInt16(span); // 从切片中读取
✅ 使用 ReadOnlySpan<byte> 提供安全只读接口
public void Parse(ReadOnlySpan<byte> data)
{
int id = BitConverter.ToInt32(data.Slice(0, 4));
}
⚠️ 四、使用时注意事项
| 注意点 |
Span / ReadOnlySpan 的说明 |
| ❗ 不能存到字段或类属性中(生命周期限制) |
Span<T> 是栈上类型,只能用在方法内部 |
| ❗ 不能跨 async/await 使用 |
因为 Span 是栈分配类型,不能“悬空”传出去 |
❗ 不能传给 Task.Run(() => ...) 里的代码 |
会造成潜在数据悬空风险 |
✅ 用 Slice() 替代 SubArray() |
不分配新数组,避免 GC |
✅ 如果要传给只能接受 byte[] 的 API,才调用 .ToArray() |
|
✅ 总结建议
| 如果我们想... |
用这个类型 |
| 存数据、序列化、传输 |
byte[] |
| 解析数据、提取子段、性能优先 |
Span<byte> |
| 保证不被篡改的数据视图 |
ReadOnlySpan<byte> |
💡 提示
C# 的 Span<T> 系统是为了高性能低内存分配设计的。我们采集的是高频数据,我们做数据解析时越多用 Span/ReadOnlySpan,GC 压力就越小、CPU 越轻松。