c# 8.0 的 range 和 index 仅对显式支持的类型有效:t[]、string、span、readonlyspan 原生支持;list 不支持切片;arraysegment 需转 span 才能用;自定义类型须实现对应索引器。

Range 和 Index 是 C# 8.0 起引入的语法糖,不是万能切片工具——它只在目标类型显式支持时才生效,用错类型会直接编译失败或意外分配内存。
哪些类型能直接用 [^n] 和 [a..b]
只有实现了对应索引器的类型才能用,不是所有“有长度”的集合都行:
-
T[]、string、Span<t></t>、ReadOnlySpan<t></t>:原生支持,零分配(Span)或堆分配(数组) -
List<t></t>:支持[i]索引,但 不支持[1..3]—— 编译报错CS0021: Cannot apply indexing with [] to an expression of type 'List<t>'</t> -
ArraySegment<byte></byte>:不支持直接切片;需先转Span<byte></byte>(.AsSpan()仅在 .NET Core 2.1+ / .NET 5+ 可用),否则要手动构造new Span<byte>(seg.Array, seg.Offset, seg.Count)</byte> - 自定义类型:必须同时提供
this[Index]和this[Range]索引器,且实现Slice(int, int)方法
^ 和 .. 的行为细节与陷阱
看似简单,但几个边界点容易引发运行时异常或语义错误:
-
^0是非法的,运行时抛IndexOutOfRangeException;^1是最后一个元素,^2是倒数第二个 - 范围是左闭右开:
arr[1..3]取索引 1 和 2,共 2 个元素;等价于arr.Skip(1).Take(2),但性能更好(无迭代器开销) - 省略写法合法:
arr[..3]=arr[0..3],arr[2..]=arr[2..arr.Length],arr[..]= 全量视图(对Span是零成本) - 字符串切片可能破坏 UTF-16 代理对:
"??".AsSpan()[0..1]可能只取到高代理项(\ud83d),导致后续ToString()显示 或Utf8Bytes转换失败
性能差异:数组切片 vs Span<t></t> 切片
同一段切片语法,底层行为天差地别:
-
int[] arr = {1,2,3,4,5}; var slice = arr[1..4];→ 返回新数组(堆分配,GC 压力),修改slice不影响arr -
Span<int> span = arr.AsSpan(); var slice = span[1..4];</int>→ 返回栈上视图(无分配,零拷贝),修改slice[i]即修改原数组对应位置 - 高频场景(如协议解析、日志截断)务必优先用
Span<t></t>+..,避免隐式堆分配 -
string的[a..b]总是返回新字符串(不可变),但str.AsSpan()[a..b]是零成本视图 —— 后者适合做校验、查找,前者才适合长期持有
ArraySegment<t></t> 和 Range 混用的常见翻车点
很多人想把老 API 返回的 ArraySegment<byte></byte> 快速切片,结果踩坑:
-
seg.AsSpan()[10..20]在 .NET Standard 2.0 中不存在 —— 项目若没升到 .NET Core 2.1+ 或 .NET 5+,这行代码根本编译不过 -
seg.Array可能为null(比如构造时传了null数组),直接调用.AsSpan()会抛NullReferenceException - 误以为
ArraySegment<t></t>是切片类型:它只是个带Offset和Count的结构体,越界访问不抛异常,读到脏数据或崩溃,和Span<t></t>的安全边界检查完全不同 - 正确做法:先判空
if (seg.Array != null),再用new Span<byte>(seg.Array, seg.Offset, seg.Count)</byte>构造视图,之后才能放心用[a..b]
最易被忽略的一点:切片语法本身不保证线程安全。无论用 Span<t></t> 还是数组,只要底层数据被其他线程并发修改,切片视图看到的就是未定义状态 —— 它不加锁,也不复制,只是指针偏移。










