18 扩展索引和切片

查看说明: 此新子句(目前(§18)暂时放置在此处,以避免由于在 PR 不受影响章节和子句中重新编号而发生文本更改。 尚未确定其最终位置,但在数组(§17)和接口(§19)章节之间可能适用 - 可以在评审期间建议其他位置。 稍后只需对它进行简单的编辑 clauses.json即可重新定位。

18.1 常规

此子句为构建的 扩展可索引可切片集合 类型引入了模型:

  • 此子句 System.Index§18.2)和 System.Range§18.3)中引入的类型:
  • 预定义的一元 ^ 运算符(§12.9.6)和二进制 ..§12.10)运算符;和
  • element_access表达式。

在模型中,类型被归类为:

  • 如果 集合 表示一组 元素,则为集合
  • 如果 扩展的可索引 集合支持 element_access 表达式,该表达式具有返回和/或设置类型单个元素的 Index 单个参数表达式(按值或引用);以及
  • 如果 扩展的可切片 集合支持 element_access 表达式,该表达式具有一个类型 Range 参数表达式,该表达式按值返回类型元素的 切片

注意:模型不需要可以设置类型的切片,但类型可能支持它作为模型的扩展。 尾注

单维数组(§12.8.12.2)和字符串(§12.8.12.3)支持模型。

该模型可由任何类、结构或接口类型支持,该类型提供实现模型语义的相应索引器(§15.9)。

为不直接支持模型但提供特定 成员模式§18.4)的类型提供隐式支持。 此支持是基于模式的,而不是基于语义的,因为假定其所 基于 的类型成员的语义 – 该语言不强制或检查这些类型成员的语义。

出于此子句的目的,定义了以下术语:

  • 集合是表示一组元素的类型。
  • 可计数集合是一个可计数属性int,它提供一个 -valued 实例属性,其值是组中当前元素的数目。 此属性应命名为或 LengthCount。 如果两者都存在,则选择前者。
  • 序列可索引类型是集合:
    • 这是可计数的;
    • 如果每个元素都可以使用具有单个必需int参数的element_access表达式进行访问,则允许从头开始索引访问其他可选参数;
    • 如果也可以使用element_access表达式设置每个元素,则序列是可修改的;
    • 元素的 from-start 索引是序列中元素之前的元素数,对于包含 N 个元素的序列:
      • 第一个元素和最后一个元素的索引分别为 0 和 N-1,以及
      • 过去结束索引(表示最后一个索引之后的假设元素)具有值 N
  • 从端索引表示相对于过去结束索引的序列中元素的位置。 对于包含 N 元素的序列,第一个、最后一个和过去结束的索引分别为 N、1 和 0。
  • 范围是从序列中的任何索引处开始的连续运行,即零个或多个索引。
  • 切片是区域内元素的集合。
  • 可切片集合是一个:
    • 可计数;
    • 提供一个方法 Slice ,该方法采用两 int 个参数来指定一个范围,分别作为起始索引和元素计数,并返回从区域中的元素构造的新切片。

上述定义已扩展供使用 IndexRange 如下所示:

  • 如果支持采用单个必需Index参数而不是参数的element_access表达式,则类型也是int序列。 在需要区分的情况下,类型称为 可扩展索引
  • 如果采用单个必需Range参数(而不是Slice方法)的element_access表达式,则类型也可以切片。 如果需要区分,类型称为 扩展切片

类型是否被归类为可计数、可索引或可切片,都受成员可访问性(§7.5)的约束,因此取决于所使用的类型。

示例:一种类型,其中可计数属性和/或索引器 protected 只是其自身成员和任何派生类型的序列。 示例结束

要限定为序列或可切片的类型所需的成员可以继承。

示例:在以下代码中

public class A
{
    public int Length { get { … } }
}

public class B : A
{
    public int this(int index) { … }
}

public class C : B
{
    public int[] Slice(int index, int count) { … }
}

类型 A 可计数, B 是一个序列,可 C 切片,也是一个序列。

示例结束

注意:

  • 由于缺少(可访问)索引器,类型可以切片,而无需编制索引。
  • 要使类型可切片和/或可索引,需要类型可计数。
  • 虽然序列的元素按序列中 的位置 进行排序,但元素本身不需要按其值进行排序,甚至按可排序。

尾注

18.2 索引类型

System.Index 类型表示一个 抽象 索引,它是 从头索引从端索引

    public readonly struct Index : IEquatable<Index>
    {
        public int Value { get; }
        public bool IsFromEnd { get; }

        public Index(int value, bool fromEnd = false);

        public static implicit operator Index(int value);
        public int GetOffset(int length);
        public bool Equals(Index other);
    }

Index 值是从一个 int构造的,指定非负偏移量,以及一个 bool,指示偏移量是来自结束(true)还是开始(false)。 如果指定的偏移量为负 ArgumentOutOfRangeException 值,则会引发。

示例

Index first = new Index(0, false); // first element index
var last = new Index(1, true);     // last element index
var past = new Index(0, true);     // past-end index

Index invalid = new Index(-1);     // throws ArgumentOutOfRangeException

示例结束

从中生成自开始索引的隐式转换intIndex,以及从其生成自结束索引的语言定义的一元运算符^§12.9.6)。intIndex

示例

可以使用隐式转换和一元 ^ 运算符编写上述示例:

Index first = 0; // first element index
var last = ^1;   // last element index
var past = ^0;   // past-end index

示例结束

该方法GetOffset从抽象Index值转换为指定length序列的具体int索引值。 如果值为 from-end,则此方法返回的值与 相同 ,否则返回的值与 相同。

此方法检查返回值是否在有效范围内0(含)。length-1

注意: 未指定检查,因为预期使用结果将索引到具有 length 元素的序列中,并且索引作应执行适当的检查。 尾注

Index IEquatable<Index>根据抽象值可以比较实现和值是否相等;Index如果两个值相等且仅当各自的ValueIsFromEnd属性相等时,两个值是相等的。 但是 Index ,不会对值进行排序,也没有提供其他比较作。

注意:Index 值是无序的,因为它们是抽象索引,因此通常无法确定从结束索引之前还是之后,而不引用序列长度。 一旦转换为具体索引,例如 GetOffset,这些具体索引是可比的。 尾注

Index值可以直接用于element_access表达式(§12.8.12)的argument_list,即:

  • 数组访问和目标是单维数组(§12.8.12.2):
  • 字符串访问 (§12.8.12.3
  • 索引器访问和目标类型具有具有类型 Index§12.8.12.4)或值可隐式转换为的类型 Index 的索引器;或者
  • 索引器访问和目标类型符合指定隐式 Index 支持的序列模式(§18.4.2)。

18.3 范围类型

System.Range类型表示从Start索引到索引但不包括End索引的Index抽象范围。

    public readonly struct Range : IEquatable<Index>
    {
        public Index Start { get; }
        public Index End { get; }

        public Range(Index start, Index end);

        public (int Offset, int Length) GetOffsetAndLength(int length);
        public bool Equals(Range other);
    }

Range 值是从两 Index 个值构造的。

示例

以下示例使用从 (§18.2) 和 ^§12.9.6) 运算符隐式转换intIndex 为每个Range运算符创建Index值:

var firstQuad = new Range(0, 4);  // the indices from `0` to `3`
                                  // int values impicitly convert to `Index`
var nextQuad = new Range(4, 8);   // the indices from `4` to `7`
var wholeSeq = new Range(0, ^0);  // the indices from `0` to `N-1` where `N` is the
                                  // length of the sequence wholeSeq is used with
var dropFirst = new Range(1, ^0); // the indices from `1` to `N-1`
var dropLast = new Range(0, ^1);  // the indices from `0` to `N-2`
var maybeLast = new Range(^1, 6); // the indices from `N-1` to 5
var lastTwo = new Range(^2, ^0);  // the indices from `N-2` to `N-1`

示例结束

语言定义的运算符..§12.10)从Index值创建值Range

示例

..可以使用运算符编写上述示例:

var firstQuad = 0..4;  // the indices from `0` to `3`
var nextQuad = 4..8;   // the indices from `4` to `7`
var wholeSeq = 0..^0;  // the indices from `0` to `N-1`
var dropFirst = 1..^0; // the indices from `1` to `N-1`
var dropLast = 0..^1;  // the indices from `0` to `N-2`
var maybeLast = ^1..6; // the indices from `N-1` to 5
var lastTwo = ^2..^0;  // the indices from `N-2` to `N-1`

示例结束

作数 .. 是可选的,第一个默认 0为,第二个默认为 ^0

示例

可以通过依赖作数的默认值来缩短上述五个示例:

var firstQuad = ..4; // the indices from `0` to `3`
var wholeSeq = ..;   // the indices from `0` to `N-1`
var dropFirst = 1..; // the indices from `1` to `N-1`
var dropLast = ..^1; // the indices from `0` to `N-2`
var lastTwo = ^2..;  // the indices from `N-2` to `N-1`

示例结束

Range值对于长度 L有效(如果:

  • 属性 LRangeStart 的具体索引,范围 End 为 0 到 L;并且
  • 的具体索引 Start 不大于具体索引 End

具有参数length的方法GetOffsetAndLength将抽象Range值转换为由元组表示的具体Range值。 Range length如果该方法无效,则ArgumentOutOfRangeException引发该方法。

返回的具体 Range 元组是一对形式 (S, N) ,其中:

  • S是范围的起始偏移量,是 属性Range的具体索引Start;
  • N 是范围中的项数,是具体索引 EndStart 属性之间的差异;
  • 要计算的两个值都与 length.

如果为零,则具体范围值为N。 空混凝土范围的值可能 S 等于具体过去的索引(§18.1),非空范围可能不是。 Range当用于对集合进行切片(§18.1)的集合有效且为空时,生成的切片为空集合。

注意: 上述结果是 Range ,与零相关的值有效且为空 length ,可用于对空集合进行切片,并导致空切片。 这不同于索引,如果集合为空,则会引发异常。 end note*

示例

将上面定义的变量与以下项结合使用 GetOffSetAndLength(6)

var (ix0, len0) = firstQuad.GetOffsetAndLength(6); // ix0 = 0, len0 = 4
var (ix1, len1) = nextQuad.GetOffsetAndLength(6);  // throws ArgumentOutOfRangeException
                                                   // as range crosses sequence end
var (ix2, len2) = wholeSeq.GetOffsetAndLength(6);  // ix2 = 0, len2 = 6
var (ix3, len3) = dropFirst.GetOffsetAndLength(6); // ix3 = 1, len3 = 5
var (ix4, len4) = dropLast.GetOffsetAndLength(6);  // ix4 = 0, len4 = 5
var (ix5, len5) = maybeLast.GetOffsetAndLength(6); // ix5 = 5, len5 = 1
var (ix6, len6) = lastTwo.GetOffsetAndLength(6);   // ix6 = 4, len6 = 2

Range IEquatable<Range>根据抽象值可以比较实现和值是否相等;Range如果两个值相等且仅当相应Start属性End的抽象值相等(§18.2)时,两个值是相等的。 但是 Range ,不会对值进行排序,也没有提供其他比较作。

注意:Range 值是无序的,因为它们是抽象的,并且没有唯一的排序关系。 转换为具体开始和长度后, GetOffsetAndLength可以定义排序关系。 尾注

Range值可以直接用于element_access表达式(§12.8.12)的argument_list,即:

  • 数组访问和目标是单维数组(§12.8.12.2):
  • 字符串访问 (§12.8.12.3):
  • 索引器访问和目标类型具有具有类型 Range§12.8.12.4)或值可隐式转换为的类型 Range 的索引器;或者
  • 索引器访问(§12.8.12.4)和目标类型符合指定隐式 Range 支持的序列模式(§18.4.3)。

18.4 基于模式的对索引和范围的隐式支持

18.4.1 常规

如果窗体E[A]的element_access表达式(§12.8.12);其中具有E类型TA是一个隐式转换为Index或无法转换为的Range单个表达式,则无法标识为:

如果符合特定模式,则提供 T 对表达式的隐式支持。 如果 T 不符合此模式,则会发生编译时错误。

18.4.2 隐式索引支持

如果在任何上下文中,element_access表达式(§12.8.12)的形式E[A];其中具有E类型TA是一个隐式可转换为Index的表达式;无效(§18.4.1),则在同一上下文中:

  • T 提供符合 序列 的可访问成员(§18.1);和
  • 表达式 E[0] 有效,并且使用相同的索引器来限定 T 为序列

然后,应隐式支持表达式 E[A]

如果不限制此标准的实现,表达式的计算顺序应等效于:

  1. E 计算结果;
  2. A 计算结果;
  3. 计算的可计数属性 T (如果实现需要);
  4. 调用同一上下文中使用的基于索引E[0]器的 T get 或 set 访问器int

18.4.3 隐式范围支持

如果在任何上下文中,element_access表达式(§12.8.12)的形式E[A];其中具有E类型TA是一个隐式可转换为Range的表达式;无效(§18.4.1),则在同一上下文中:

  • T提供可计数和可切片可访问成员(§18.1

然后,应隐式支持表达式 E[A]

如果不限制此标准的实现,表达式的计算顺序应等效于:

  1. E 计算结果;
  2. A 计算结果;
  3. 计算的可计数属性 T (如果实现需要);
  4. 调用 Slice 的方法 T