<p>Index 和 Range 是 C# 8.0 引入的独立值类型结构体,非 int 的语法糖;^n 合法范围为 ^1 至 ^length,^0 编译报错;Range 切片返回新数组,有堆分配开销。</p>

Index 和 Range 是什么类型,为什么不能直接当 int 用
Index 和 Range 是 C# 8.0 引入的独立结构体类型,不是语法糖的“假类型”,而是有实际行为的值类型。它们不隐式转换为 int,所以写 arr[index] 时,index 必须是 Index 类型(比如 ^3),不能是 int 变量再强转——编译器会报错 Cannot convert from 'int' to 'System.Index'。
常见错误:把 var i = ^3; 误以为是 int,然后传给自定义方法 expecting int 参数,结果编译失败。
实操建议:
- 要用
Index就显式声明:Index i = ^3;或直接用字面量^n - 需要转成实际下标时,调用
i.GetValueFromLength(len),例如^3.GetValueFromLength(10) == 7 -
Range同理,range.Start.GetValueFromLength(len)和range.End.GetValueFromLength(len)才是真实索引
Range 切片后得到的是新数组还是视图
C# 的 arr[1..4] 返回的是一个 新分配的数组,不是 Span
使用场景判断很关键:
- 临时取几项做计算、日志、简单转换 → 用
Range没问题,代码干净 - 要避免复制(如解析二进制流、高性能图像处理)→ 改用
Span<t></t>或Memory<t></t>,配合array.AsSpan()[1..4] - 想复用已有缓冲区又需切片逻辑 → 直接操作
Span,别碰Range语法
注意:string 的 [Range] 也是返回新字符串,同样涉及内存分配。
^ 运算符的边界行为和常见越界陷阱
^n 表示“从末尾往前数第 n 个”,但它的合法范围是 ^1 到 ^length(含),^0 是非法的,编译直接报错 Index must be positive。
容易踩的坑:
- 动态计算
^k时没校验k > 0 && k ,运行时报 <code>Index was outside the bounds of the array - 误以为
^0等价于array.Length—— 实际上^1才是最后一个元素,^array.Length才是第一个 - 在
Range中混用正负索引:arr[^5..^1]合法,但arr[^5..3]会被解释为arr[^5..^array.Length + 3],语义易混淆,建议统一用^或全用正数
和 LINQ Take/Skip 对比:什么时候该用 Range
Range 是语言级切片,编译后直接转为 Array.Copy 或类似高效拷贝;而 Take(3).Skip(2) 返回 IEnumerable<t></t>,延迟执行、可能多次枚举、且无长度预知。
选 Range 更合适的情况:
- 明确知道源是数组或
string,且需要一份独立副本 - 切片逻辑固定(如取后三位:
str[^3..]),而非依赖运行时条件拼接 - 性能敏感,且切片范围小、频次高 ——
Range比等效 LINQ 快 3–5 倍(实测,.NET 6+)
别硬套 Range 的情况:
- 源是
IEnumerable<t></t>(比如数据库查询结果、文件流行迭代器)→ 它不支持Range,编译不过 - 切片逻辑复杂,带过滤或状态判断 → 还是老实用 LINQ 或 for 循环更清晰
真正容易被忽略的一点:Range 不支持多维数组,matrix[1..3, 2..4] 是非法的,必须降维或手动循环处理。


















