arraysegment 是仅记录起始位置和长度的数组视图,不复制数据、无内存安全检查,越界不抛异常;range/ index 是 c# 8.0 语法糖,依赖类型实现对应索引器,字符串切片需防代理对越界。

ArraySegment 不是切片,只是“视图”
它不复制数据,也不改变原数组,只记录起始位置和长度。很多人误以为它能像 Span<t></t> 那样安全地做局部操作,但 ArraySegment<t></t> 没有内存安全检查,越界访问不会抛异常,而是读到脏数据或崩溃。
- 只适合传给老 API(比如
Socket.SendAsync(ArraySegment<byte>)</byte>),新代码优先用Span<t></t>或Memory<t></t> -
ArraySegment<t>.Array</t>可能为null(比如构造时传了null数组),用前必须判空 - 不能直接用
[i]索引访问 —— 要手动加Offset:比如seg.Array[seg.Offset + i] - 没有
Length属性以外的长度相关方法,也不支持foreach(除非显式转成IEnumerable<t></t>)
Range 和 Index 是语法糖,底层靠编译器重写
它们本身不是类型,只是 C# 8.0 引入的语法,编译后变成 Range 或 Index 类型的实例,再由目标类型的 get_Slice 或 get_Item 方法处理。所以不是所有类型都支持 —— 只有实现了对应索引器的类型才行。
- 数组、
string、Span<t></t>、ReadOnlySpan<t></t>原生支持;List<t></t>不支持,会编译失败 -
^n表示从末尾往前数第 n 个(^1是最后一个),但^0是非法的,运行时报IndexOutOfRangeException -
..左右都可以省略:[..3]等价于[0..3],[3..]等价于[3..Length] - 负索引在运行时才计算真实位置,所以
arr[^i]比arr[arr.Length - i]多一次判断开销(微小,但高频场景可感知)
字符串切片要注意编码边界
string 是 UTF-16 编码,一个 Unicode 字符可能占两个 char(即代理对)。用 Range 切片时,如果切在代理对中间,会得到无效的 char 序列,ToString() 可能显示 ,甚至后续 Span<char>.Utf8Bytes</char> 转换出错。
- 安全做法是先用
StringInfo或Rune(.NET 5+)按字符(grapheme cluster)拆分,再索引 - 简单场景下,避免对用户输入的
string直接用^或..切,尤其涉及表情符号或中文标点时 -
"abc".AsSpan()[1..]没问题,但"??".AsSpan()[0..1]可能只取到高代理项,结果不可靠
ArraySegment 和 Range 混用容易掉坑
有人想把 ArraySegment<byte></byte> 转成 Span<byte></byte> 再用 Range,但注意:new Span<byte>(seg.Array, seg.Offset, seg.Count)</byte> 才等价,直接 seg.AsSpan() 在 .NET Core 2.1+ 才有 —— 旧框架要自己构造。
-
ArraySegment<t>.AsSpan()</t>在 .NET Standard 2.0 中不存在,需升级到 .NET Core 2.1+ 或 .NET 5+ - 切片后的
Span<t></t>仍绑定原数组内存,若原数组被 GC 移动(比如没 pin 住),而你又把它传给了 native 代码,就危险了 - 别对
ArraySegment<t></t>的Array直接用[start..end]—— 它切的是整个数组,不是 segment 范围
事情说清了就结束。最常被忽略的是:Range 切字符串时默认按 char 计,不是按字符;ArraySegment 的 “零拷贝” 假象背后没有边界保护。











