如何为长度未知的列表实现Range的嵌套范围计算?
C# Range嵌套切片的实现问题
我正在构建一个扩展方法库,希望在合适场景中集成Range对象的使用。因此创建了一个类,用于表示任意列表的轻量级视图,该类基于给定的Range定义:
public static class ListSlice { public static IReadOnlyList<T> Create<T>(IReadOnlyList<T> list, Range range) => new ListSlice<T>(list, range); public static IReadOnlyList<T> Create<T>(IReadOnlyList<T> list, Index start, Index end) => Create(list, new Range(start, end)); } public class ListSlice<T> : IReadOnlyList<T> { public ListSlice(IReadOnlyList<T> list, Range range) { (List, Range) = list is ListSlice<T> slice ? (slice.List, slice.Range.Apply(range)) // 此处尝试直接组合Range,存在问题 : (list, range); } public IReadOnlyList<T> List { get; } public Range Range { get; } public T this[int index] => List[Range.GetOffsetAndLength(List.Count).Offset + index]; public int Count => Range.GetOffsetAndLength(List.Count).Length; public IEnumerator<T> GetEnumerator() { var (offset, length) = Range.GetOffsetAndLength(List.Count); foreach (var i in Enumerable.Range(0, length)) yield return List[offset + i]; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }
核心问题
现在需要实现切片的切片功能:已知可以通过原始列表长度计算新的有效范围,但仅通过Range对象本身能否完成这种嵌套计算?
当Range的起始/结束为绝对数值时,计算逻辑很简单,但遇到包含相对结束位置(如^n)、开放式Range(如0..)的场景时,无法直接理清计算逻辑。比如:
// 绝对起始/结束,长度可直接通过二者差值得到 0..20 // 长度: 20 5..10 // 长度: 5 5..15 // 长度: 10 // 起始和结束均为相对结束位置时,长度仍是二者差值 ^20..^0 // 长度: 20 ^15..^10 // 长度: 5 ^15..^5 // 长度: 10 // 开放式Range的长度必须结合集合长度才能确定 0..^10 // 长度: 集合长度 - 10 ^5..20 // 长度: 20 - (集合长度 - 5) = 25 - 集合长度(需保证结果非负) 0.. // 长度: 集合长度(等价于 0..^0)
我曾尝试用固定的虚拟长度来计算,但这种方式会遗漏很多场景:
public static class RangeExtensions { public static Range Apply(this Range range, int baseLength, Range subRange) { var (offset1, length1) = range.GetOffsetAndLength(baseLength); var (offset2, length2) = subRange.GetOffsetAndLength(length1); return (offset1 + offset2)..(offset1 + offset2 + length2); } public static Range Apply(this Range range, Range subRange) { // 简单场景: (5..10)(..2) -> (5..7) // 复杂场景: (5..10)(^2..) -> ??? var dummyLength = 100; // 此处用固定值会导致错误 var (offset1, length1) = range.GetOffsetAndLength(dummyLength); var (offset2, length2) = subRange.GetOffsetAndLength(length1); var offset = offset1 + offset2; return offset..(offset + length2); // 结果依赖虚拟长度,不可靠 } }
解决方案
结论:仅靠Range对象本身无法完成嵌套计算,因为Range本质是一个“解析规则”,相对索引和开放式Range的实际偏移/长度必须结合具体集合的长度才能确定。
正确的做法是在ListSlice<T>的构造函数中,始终基于原始列表的实际长度来计算最终的有效Range:
public ListSlice(IReadOnlyList<T> list, Range range) { if (list is ListSlice<T> slice) { // 1. 获取原切片在原始列表上的偏移和长度 var (baseOffset, baseLength) = slice.Range.GetOffsetAndLength(slice.List.Count); // 2. 计算子Range在原切片长度下的偏移和长度 var (subOffset, subLength) = range.GetOffsetAndLength(baseLength); // 3. 推导最终在原始列表上的Range var finalStart = baseOffset + subOffset; var finalEnd = finalStart + subLength; List = slice.List; Range = finalStart..finalEnd; } else { List = list; Range = range; } }
为什么这个方案可行?
- 每次嵌套切片都回溯到原始列表的长度,确保所有Range(包括相对、开放式)都能被正确解析
- 避免了虚拟长度带来的误差,所有计算都基于真实的集合长度
- 最终的
Range直接对应原始列表的范围,后续的索引访问、枚举都能直接正确映射
补充说明
- 任何
Range的长度都不能脱离具体集合长度独立计算,因为相对索引和开放式Range的语义本身就依赖于目标集合的长度 - 如果需要单独处理Range的嵌套解析,必须传入对应的基准长度(即原Range对应的集合长度),这也是
Apply(this Range range, int baseLength, Range subRange)方法可行的原因——只要传入真实的baseLength,就能得到正确结果
内容的提问来源于stack exchange,提问作者Jeff Mercado
相关产品推荐
相关产品推荐

