arraysegment 是轻量级结构体,仅引用原数组、偏移和长度,适用于网络包解析等需零拷贝的场景;但无边界防护、不防修改、不支持栈内存,且需谨慎管理数组生命周期。

ArraySegment 是什么,什么时候该用它
ArraySegment<t></t> 不是数组的“切片副本”,而是一个轻量级结构体,只保存对原数组的引用、起始偏移和长度。它不分配新内存,也不复制数据——适合高频传递子区间但又不想触发 GC 或内存拷贝的场景,比如网络包解析、流式数据处理、协议帧拆分。
但要注意:它不提供边界防护,越界访问仍会抛 IndexOutOfRangeException;且它不继承 IList<t></t>(.NET Core 2.1+ 才支持隐式转换为 ReadOnlySpan<t></t>)。
常见误用点:
- 以为
ArraySegment<t></t> 能防止原数组被修改 —— 实际上它跟原数组共享内存,写操作会直接影响原数组
- 在异步回调中长期持有
ArraySegment<byte></byte>,却忽略了底层数组可能已被池化回收(如 ArrayPool<byte>.Shared</byte> 分配的数组)
- 用它替代
Span<t></t> 处理栈上数据(如栈变量数组),结果编译失败 —— ArraySegment<t></t> 只能包装堆数组
如何安全地创建和传递 ArraySegment
创建时必须校验参数合法性,尤其在网络 I/O 中,接收缓冲区长度常动态变化:
byte[] buffer = new byte[8192];
int bytesRead = socket.Receive(buffer);
if (bytesRead > 0)
{
// ✅ 正确:显式指定 offset 和 count
var segment = new ArraySegment<byte>(buffer, 0, bytesRead);
// ❌ 危险:若 bytesRead == 0,new ArraySegment<byte>(buffer, 0, 0) 合法,
// 但后续传给某些解析器(如 Protobuf)可能因空段抛 NullReferenceException
}
传递时优先考虑方法签名是否接受 ArraySegment<t></t>。如果目标 API 只接受 IEnumerable<t></t> 或 T[],别强行转换 —— 那会触发装箱或复制。更合理的做法是改用 Span<t></t>(.NET Core 2.1+)或直接传原数组+参数。
ArraySegment 和 Span、Memory 怎么选
三者都表示“内存区域”,但语义与能力不同:
-
ArraySegment<t></t>:仅限堆数组,无生命周期管理,无栈支持,兼容旧框架(.NET Framework 4.5+)
-
Span<t></t>:可指向栈内存(stackalloc)、数组、unmanaged 内存,但必须在同步方法内使用(不能跨 await 边界)
-
Memory<t></t>:Span<t></t> 的“可等待”对应物,支持异步场景,底层可包装 ArraySegment<t></t> 或 ArrayPool<t>.Rent()</t> 返回的数组
如果你在写高性能网络库且目标是 .NET 5+,优先用 Memory<byte></byte>;若需兼容 .NET Framework 或无法升级运行时,ArraySegment<byte></byte> 仍是务实选择,但得自己管好数组生命周期。
容易被忽略的性能陷阱
ArraySegment<t></t> 自身开销极小(仅 3 个字段),但实际性能瓶颈常出现在下游:
- 调用
.ToArray() —— 这会复制整个段,完全抵消掉零拷贝优势
- 用
segment.Count 做循环条件,却在循环体内反复访问 segment.Array[i + segment.Offset],不如先缓存 segment.Array 和 segment.Offset
- 将
ArraySegment<byte></byte> 存入字典或队列作为 key/value,导致大量装箱(它是值类型,但集合接口常要求引用类型)
真正省下的不是 CPU 周期,而是 GC 压力。如果你的吞吐瓶颈在内存分配而非计算,ArraySegment<t></t> 才值得投入。否则,先 profile,再优化。
ArraySegment<t></t> 不是数组的“切片副本”,而是一个轻量级结构体,只保存对原数组的引用、起始偏移和长度。它不分配新内存,也不复制数据——适合高频传递子区间但又不想触发 GC 或内存拷贝的场景,比如网络包解析、流式数据处理、协议帧拆分。
但要注意:它不提供边界防护,越界访问仍会抛 IndexOutOfRangeException;且它不继承 IList<t></t>(.NET Core 2.1+ 才支持隐式转换为 ReadOnlySpan<t></t>)。
常见误用点:
- 以为
ArraySegment<t></t>能防止原数组被修改 —— 实际上它跟原数组共享内存,写操作会直接影响原数组 - 在异步回调中长期持有
ArraySegment<byte></byte>,却忽略了底层数组可能已被池化回收(如ArrayPool<byte>.Shared</byte>分配的数组) - 用它替代
Span<t></t>处理栈上数据(如栈变量数组),结果编译失败 ——ArraySegment<t></t>只能包装堆数组
如何安全地创建和传递 ArraySegment
创建时必须校验参数合法性,尤其在网络 I/O 中,接收缓冲区长度常动态变化:
byte[] buffer = new byte[8192];
int bytesRead = socket.Receive(buffer);
if (bytesRead > 0)
{
// ✅ 正确:显式指定 offset 和 count
var segment = new ArraySegment<byte>(buffer, 0, bytesRead);
// ❌ 危险:若 bytesRead == 0,new ArraySegment<byte>(buffer, 0, 0) 合法,
// 但后续传给某些解析器(如 Protobuf)可能因空段抛 NullReferenceException
}
传递时优先考虑方法签名是否接受 ArraySegment<t></t>。如果目标 API 只接受 IEnumerable<t></t> 或 T[],别强行转换 —— 那会触发装箱或复制。更合理的做法是改用 Span<t></t>(.NET Core 2.1+)或直接传原数组+参数。
ArraySegment 和 Span、Memory 怎么选
三者都表示“内存区域”,但语义与能力不同:
-
ArraySegment<t></t>:仅限堆数组,无生命周期管理,无栈支持,兼容旧框架(.NET Framework 4.5+)
-
Span<t></t>:可指向栈内存(stackalloc)、数组、unmanaged 内存,但必须在同步方法内使用(不能跨 await 边界)
-
Memory<t></t>:Span<t></t> 的“可等待”对应物,支持异步场景,底层可包装 ArraySegment<t></t> 或 ArrayPool<t>.Rent()</t> 返回的数组
如果你在写高性能网络库且目标是 .NET 5+,优先用 Memory<byte></byte>;若需兼容 .NET Framework 或无法升级运行时,ArraySegment<byte></byte> 仍是务实选择,但得自己管好数组生命周期。
容易被忽略的性能陷阱
ArraySegment<t></t> 自身开销极小(仅 3 个字段),但实际性能瓶颈常出现在下游:
- 调用
.ToArray() —— 这会复制整个段,完全抵消掉零拷贝优势
- 用
segment.Count 做循环条件,却在循环体内反复访问 segment.Array[i + segment.Offset],不如先缓存 segment.Array 和 segment.Offset
- 将
ArraySegment<byte></byte> 存入字典或队列作为 key/value,导致大量装箱(它是值类型,但集合接口常要求引用类型)
真正省下的不是 CPU 周期,而是 GC 压力。如果你的吞吐瓶颈在内存分配而非计算,ArraySegment<t></t> 才值得投入。否则,先 profile,再优化。
byte[] buffer = new byte[8192];
int bytesRead = socket.Receive(buffer);
if (bytesRead > 0)
{
// ✅ 正确:显式指定 offset 和 count
var segment = new ArraySegment<byte>(buffer, 0, bytesRead);
// ❌ 危险:若 bytesRead == 0,new ArraySegment<byte>(buffer, 0, 0) 合法,
// 但后续传给某些解析器(如 Protobuf)可能因空段抛 NullReferenceException
}
传递时优先考虑方法签名是否接受 ArraySegment<t></t>。如果目标 API 只接受 IEnumerable<t></t> 或 T[],别强行转换 —— 那会触发装箱或复制。更合理的做法是改用 Span<t></t>(.NET Core 2.1+)或直接传原数组+参数。
ArraySegment 和 Span、Memory 怎么选
三者都表示“内存区域”,但语义与能力不同:
-
ArraySegment<t></t>:仅限堆数组,无生命周期管理,无栈支持,兼容旧框架(.NET Framework 4.5+)
-
Span<t></t>:可指向栈内存(stackalloc)、数组、unmanaged 内存,但必须在同步方法内使用(不能跨 await 边界)
-
Memory<t></t>:Span<t></t> 的“可等待”对应物,支持异步场景,底层可包装 ArraySegment<t></t> 或 ArrayPool<t>.Rent()</t> 返回的数组
如果你在写高性能网络库且目标是 .NET 5+,优先用 Memory<byte></byte>;若需兼容 .NET Framework 或无法升级运行时,ArraySegment<byte></byte> 仍是务实选择,但得自己管好数组生命周期。
容易被忽略的性能陷阱
ArraySegment<t></t> 自身开销极小(仅 3 个字段),但实际性能瓶颈常出现在下游:
- 调用
.ToArray() —— 这会复制整个段,完全抵消掉零拷贝优势
- 用
segment.Count 做循环条件,却在循环体内反复访问 segment.Array[i + segment.Offset],不如先缓存 segment.Array 和 segment.Offset
- 将
ArraySegment<byte></byte> 存入字典或队列作为 key/value,导致大量装箱(它是值类型,但集合接口常要求引用类型)
真正省下的不是 CPU 周期,而是 GC 压力。如果你的吞吐瓶颈在内存分配而非计算,ArraySegment<t></t> 才值得投入。否则,先 profile,再优化。
-
ArraySegment<t></t>:仅限堆数组,无生命周期管理,无栈支持,兼容旧框架(.NET Framework 4.5+) -
Span<t></t>:可指向栈内存(stackalloc)、数组、unmanaged内存,但必须在同步方法内使用(不能跨 await 边界) -
Memory<t></t>:Span<t></t>的“可等待”对应物,支持异步场景,底层可包装ArraySegment<t></t>或ArrayPool<t>.Rent()</t>返回的数组
Memory<byte></byte>;若需兼容 .NET Framework 或无法升级运行时,ArraySegment<byte></byte> 仍是务实选择,但得自己管好数组生命周期。
容易被忽略的性能陷阱
ArraySegment<t></t> 自身开销极小(仅 3 个字段),但实际性能瓶颈常出现在下游:
- 调用
.ToArray() —— 这会复制整个段,完全抵消掉零拷贝优势
- 用
segment.Count 做循环条件,却在循环体内反复访问 segment.Array[i + segment.Offset],不如先缓存 segment.Array 和 segment.Offset
- 将
ArraySegment<byte></byte> 存入字典或队列作为 key/value,导致大量装箱(它是值类型,但集合接口常要求引用类型)
真正省下的不是 CPU 周期,而是 GC 压力。如果你的吞吐瓶颈在内存分配而非计算,ArraySegment<t></t> 才值得投入。否则,先 profile,再优化。
.ToArray() —— 这会复制整个段,完全抵消掉零拷贝优势segment.Count 做循环条件,却在循环体内反复访问 segment.Array[i + segment.Offset],不如先缓存 segment.Array 和 segment.Offset
ArraySegment<byte></byte> 存入字典或队列作为 key/value,导致大量装箱(它是值类型,但集合接口常要求引用类型)










