魔督老克拉 發表於 2021-2-25 01:43:00

C# 使用 Index 和 Range 简化集合操作

<h1 id="c-使用-index-和-range-简化集合操作">C# 使用 Index 和 Range 简化集合操作</h1>
<h2 id="intro">Intro</h2>
<p>有的语言数组的索引值是支持负数的,表示从后向前索引,比如:<code>arr[-1]</code></p>
<p>从 C# 8 开始,C# 支持了数组的反向 <code>Index</code>,和 <code>Range</code> 操作,反向 <code>Index</code> 类似于其他语言中的负索引值,但其实是由编译器帮我们做了一个转换,<code>Range</code> 使得我们对数组截取某一部分的操作会非常简单,下面来看一下如何使用吧</p>
<h2 id="sample">Sample</h2>
<p>使用 <code>^</code> 可以从集合的最后开始索引元素,如果从数组的最后开始索引元素,最后一个元素应该是 1 而不是0如: <code>arr[^1]</code></p>
<p>使用 <code>..</code> 可以基于某个数组截取集合中的某一段创建一个新的数组,比如 <code>var newArray = array</code>,再来看一下下面的示例吧</p>
<pre><code class="language-c#">int[] someArray = new int { 1, 2, 3, 4, 5 };
int lastElement = someArray[^1]; // lastElement = 5
lastElement.Dump();

someArray.Dump();

someArray.Dump();

someArray.Dump();

someArray[..^1].Dump();

someArray[..2].Dump();
</code></pre>
<p>输出结果如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/489462/202102/489462-20210225014155869-984669497.png" alt="output" loading="lazy"></p>
<h2 id="index">Index</h2>
<p>那么它是如何实现的呢,索引值引入了一个新的数据结构 <code>System.Index</code>,当你使用 <code>^</code> 运算符的时候,实际转换成了 <code>Index</code>。</p>
<p><code>Index</code>:</p>
<pre><code class="language-c#">public readonly struct Index : IEquatable&lt;Index&gt;
{
    public Index(int value, bool fromEnd = false);

    /// &lt;summary&gt;Create an Index pointing at first element.&lt;/summary&gt;
    public static Index Start =&gt; new Index(0);

    /// &lt;summary&gt;Create an Index pointing at beyond last element.&lt;/summary&gt;
    public static Index End =&gt; new Index(~0);
    //
    // Summary:
    //   Gets a value that indicates whether the index is from the start or the end.
    //
    // Returns:
    //   true if the Index is from the end; otherwise, false.
    public bool IsFromEnd { get; }
    //
    // Summary:
    //   Gets the index value.
    //
    // Returns:
    //   The index value.
    public int Value { get; }

    //
    // Summary:
    //   Creates an System.Index from the end of a collection at a specified index position.
    //
    // Parameters:
    //   value:
    //   The index value from the end of a collection.
    //
    // Returns:
    //   The Index value.
    public static Index FromEnd(int value);
    //
    // Summary:
    //   Create an System.Index from the specified index at the start of a collection.
    //
    // Parameters:
    //   value:
    //   The index position from the start of a collection.
    //
    // Returns:
    //   The Index value.
    public static Index FromStart(int value);
    //
    // Summary:
    //   Returns a value that indicates whether the current object is equal to another
    //   System.Index object.
    //
    // Parameters:
    //   other:
    //   The object to compare with this instance.
    //
    // Returns:
    //   true if the current Index object is equal to other; false otherwise.
    public bool Equals(Index other);
    //
    // Summary:
    //   Calculates the offset from the start of the collection using the given collection length.
    //
    // Parameters:
    //   length:
    //   The length of the collection that the Index will be used with. Must be a positive value.
    //
    // Returns:
    //   The offset.
    public int GetOffset(int length);

    //
    // Summary:
    //   Converts integer number to an Index.
    //
    // Parameters:
    //   value:
    //   The integer to convert.
    //
    // Returns:
    //   An Index representing the integer.
    public static implicit operator Index(int value);
}
</code></pre>
<p>如果想要自己自定义的集合支持 <code>Index</code> 这种从数组最后索引的特性,只需要加一个类型是 <code>Index</code> 的索引器就可以了,正向索引也是支持的,int 会自动隐式转换为 <code>Index</code>,除了显示的增加 <code>Index</code> 索引器之外,还可以隐式支持,实现一个 <code>int Count {get;}</code> 的属性(属性名叫 <code>Length</code> 也可以),再实现一个 <code>int</code> 类型的索引器就可以了</p>
<p>写一个简单的小示例:</p>
<pre><code class="language-c#">private class TestCollection
{
    public IList&lt;int&gt; Data { get; init; }

    public int Count =&gt; Data.Count;
    public int this =&gt; Data;

    //public int this =&gt; Data;
}
var array = new TestCollection()
{
    Data = new[] { 1, 2, 3 }
};
Console.WriteLine(array[^1]);
Console.WriteLine(array);
</code></pre>
<h2 id="range">Range</h2>
<p><code>Range</code> 是在 <code>Index</code> 的基础上实现的,<code>Range</code> 需要两个 <code>Index</code> 来指定开始和结束</p>
<pre><code class="language-c#">public readonly struct Range : IEquatable&lt;Range&gt;
{
    /// &lt;summary&gt;Represent the inclusive start index of the Range.&lt;/summary&gt;
    public Index Start { get; }

    /// &lt;summary&gt;Represent the exclusive end index of the Range.&lt;/summary&gt;
    public Index End { get; }

    /// &lt;summary&gt;Construct a Range object using the start and end indexes.&lt;/summary&gt;
    /// &lt;param name="start"&gt;Represent the inclusive start index of the range.&lt;/param&gt;
    /// &lt;param name="end"&gt;Represent the exclusive end index of the range.&lt;/param&gt;
    public Range(Index start, Index end)
    {
      Start = start;
      End = end;
    }

    /// &lt;summary&gt;Create a Range object starting from start index to the end of the collection.&lt;/summary&gt;
    public static Range StartAt(Index start) =&gt; new Range(start, Index.End);

    /// &lt;summary&gt;Create a Range object starting from first element in the collection to the end Index.&lt;/summary&gt;
    public static Range EndAt(Index end) =&gt; new Range(Index.Start, end);

    /// &lt;summary&gt;Create a Range object starting from first element to the end.&lt;/summary&gt;
    public static Range All =&gt; new Range(Index.Start, Index.End);

    /// &lt;summary&gt;Calculate the start offset and length of range object using a collection length.&lt;/summary&gt;
    /// &lt;param name="length"&gt;The length of the collection that the range will be used with. length has to be a positive value.&lt;/param&gt;
    /// &lt;remarks&gt;
    /// For performance reason, we don't validate the input length parameter against negative values.
    /// It is expected Range will be used with collections which always have non negative length/count.
    /// We validate the range is inside the length scope though.
    /// &lt;/remarks&gt;
    public (int Offset, int Length) GetOffsetAndLength(int length);
}
</code></pre>
<p>如何在自己的类中支持 <code>Range</code> 呢?</p>
<p>一种方式是自己直接实现一个类型是 <code>Range</code> 的索引器</p>
<p>另外一种方式是隐式实现,在自定义类中添加一个 <code>Count</code> 属性,然后实现一个 <code>Slice</code> 方法,<code>Slice</code> 方法有两个 <code>int</code> 类型的参数,第一个参数表示 offset,第二个参数表示 length 或者 count</p>
<p>来看下面这个示例吧,还是刚才那个类,我们支持一下 <code>Range</code>:</p>
<pre><code class="language-c#">private class TestCollection
{
    public IList&lt;int&gt; Data { get; init; }
    //public int[] this
    //{
    //    get
    //    {
    //      var rangeInfo = range.GetOffsetAndLength(Data.Count);
    //      return Data.Skip(rangeInfo.Offset).Take(rangeInfo.Length).ToArray();
    //    }
    //}

    public int Count =&gt; Data.Count;

    public int[] Slice(int start, int length)
    {
      var array = new int;
      for (var i = start; i &lt; length &amp;&amp; i &lt; Data.Count; i++)
      {
            array = Data;
      }
      return array;
    }
}
</code></pre>
<h2 id="more">More</h2>
<p>新的操作符 (<code>^</code> and <code>..</code>) 都只是语法糖,本质上是调用 <code>Index</code>、<code>Range</code></p>
<p><code>Index</code> 并不是支持负数索引,从最后向前索引只是编译器帮我们做了一个转换,转换成从前到后的索引值,借助于它,我们很多取集合最后一个元素的写法就可以大大的简化了,就可以从原来的 <code>array</code> =&gt; <code>array[^1]</code></p>
<p><code>Range</code> 在创建某个数组的子序列的时候就非常的方便, <code>newArray = array</code>,需要注意的是,<code>Range</code> 是"左闭右开"的,包含左边界的值,不包含右边界的值</p>
<p>还没使用过 <code>Index</code>/<code>Range</code> 的快去体验一下吧,用它们优化数组的操作吧~~</p>
<h2 id="references">References</h2>
<ul>
<li>https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/ranges-indexes</li>
<li>https://docs.microsoft.com/en-us/dotnet/api/system.index?view=net-5.0</li>
<li>https://docs.microsoft.com/en-us/dotnet/api/system.range?view=net-5.0</li>
<li>https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/ranges</li>
<li>https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Index.cs</li>
<li>https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Range.cs</li>
<li>https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/ArraySegment.cs</li>
</ul>


</div>
<div id="MySignature" role="contentinfo">
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。<br><br>
来源:https://www.cnblogs.com/weihanli/p/14444780.html
頁: [1]
查看完整版本: C# 使用 Index 和 Range 简化集合操作