在 C# 开发中,你是否曾为以下问题困扰?
需要处理多种内存(数组、栈内存、非托管内存)但写法各异
大量数组切片操作导致复制开销
使用 unsafe
指针时如履薄冰
Span
正是为解决这些问题而生的利器!它提供了一种统一、安全且高效的内存操作方式。
Span
的核心价值在于它自身不拥有内存,而是为现有内存提供类型安全且高性能的视图。
csharp
// 1. 基于数组的视图 byte[] buffer = new byte[1024]; Spanspan = buffer.AsSpan(); // 零复制创建视图 // 2. 直接操作栈内存(无需 fixed) Span stackSpan = stackalloc byte[64]; // 安全分配栈内存 // 3. 操作非托管内存 IntPtr unmanagedPtr = Marshal.AllocHGlobal(100); Span unmanagedSpan; unsafe { unmanagedSpan = new Span (unmanagedPtr.ToPointer(), 100); }
Span 的高效与安全源于其底层设计:
csharp
public readonly ref struct Span{ private readonly ref T _pointer; // 内部通过引用存储 private readonly int _length; // 长度信息确保安全 }
ref struct
约束:强制 Span 只能在栈上分配,防止逃逸到堆上(避免无效引用)
内部 ByRef 引用:直接指向原始内存,操作无复制开销
自动范围检查:访问时验证索引有效性,杜绝内存越界
对比传统数组切片:
csharp
// 传统方式:产生复制开销 byte[] GetSubArray(byte[] source, int start, int length) { var result = new byte[length]; Array.Copy(source, start, result, 0, length); // 内存复制! return result; } // Span 方式:零复制视图 SpanGetSubSpan(Span source, int start, int length) { return source.Slice(start, length); // 仅创建视图 }
性能测试结果(处理 1MB 数组切片 10,000 次):
方法 | 内存分配 | 耗时 |
---|---|---|
传统复制 | 10 GB | 1200 ms |
Span 切片 | 0 KB | 5 ms |
虽然 Span 很强大,但需遵循规则:
csharp
// ✅ 正确:栈上使用(局部变量) void SafeMethod() { SpanlocalSpan = stackalloc byte[64]; ProcessData(localSpan); } // ❌ 危险:尝试存储到堆 class InvalidUsage { Span _span; // 编译错误!ref struct 不能作为字段 void StoreSpan(Span span) { _span = span; // 禁止!防止视图超出原始内存生命周期 } }
高性能解析器:直接操作网络/文件缓冲区
csharp
Spandata = GetNetworkPacket(); int id = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4));
零复制算法:大文件处理避免复制
csharp
using var mmap = MemoryMappedFile.CreateFromFile("large.bin"); using var accessor = mmap.CreateViewAccessor(); unsafe { byte* ptr = null; accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); var fileSpan = new Span(ptr, (int)accessor.Capacity); }
栈分配小对象:减少 GC 压力
csharp
const int MAX_SIZE = 128; // 小内存场景 Spanbuffer = stackalloc byte[MAX_SIZE];
统一视图:通过单一 API 操作数组、栈内存、非托管内存
零复制:切片等操作不产生内存复制
内存安全:自动边界检查 + 生命周期约束
性能卓越:接近指针的速度,无 GC 压力