C# 8.0 in-depth: Understanding index and range, and working with LINQ and IEnumerable<T>

C# 8.0 introduces index and range for array. This part discussed the index and range types, syntax, compilation, and how to apply them with LINQ for any type that implements IEnumerable<T>.

Index and Range types and C# syntax

The System.Index and System.Range structures are introduced to the new .NET Standard. Index is a wrapper of int index value (non-negative int means index from start, negative int means index from the end), and Range is a tuple of start Index and end Index:

public readonly struct Index : IEquatable<Index>
{
    private readonly int _value;
</span><span style="color: blue;">public </span><span style="color: black;">Index(</span><span style="color: blue;">int </span><span style="color: black;">value, </span><span style="color: blue;">bool </span><span style="color: black;">fromEnd)
{
    </span><span style="color: blue;">if </span><span style="color: black;">(value &lt; 0)
    {
        ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
    }
    </span><span style="color: blue;">this</span><span style="color: black;">._value = fromEnd ? ~value : value;
}

</span><span style="color: blue;">public int </span><span style="color: black;">Value =&gt; </span><span style="color: blue;">this</span><span style="color: black;">._value &gt;= 0 ? </span><span style="color: blue;">this</span><span style="color: black;">._value : ~</span><span style="color: blue;">this</span><span style="color: black;">._value;

</span><span style="color: blue;">public bool </span><span style="color: black;">FromEnd =&gt; _value &lt; 0;

public static implicit operator Index(int value) => new Index(value, false);

</span><span style="color: green;">// Other members.

}

public readonly struct Range : IEquatable<Range> { private Range(Index start, Index end) { this.Start = start; this.End = end; }

</span><span style="color: blue;">public </span><span style="color: black;">Index Start { </span><span style="color: blue;">get</span><span style="color: black;">; }

</span><span style="color: blue;">public </span><span style="color: black;">Index End { </span><span style="color: blue;">get</span><span style="color: black;">; }

</span><span style="color: blue;">public static </span><span style="color: black;">Range Create(Index start, Index end) =&gt; 
    </span><span style="color: blue;">new </span><span style="color: black;">Range(start, end);

</span><span style="color: blue;">public static </span><span style="color: black;">Range All() =&gt; 
    </span><span style="color: blue;">new </span><span style="color: black;">Range(</span><span style="color: blue;">new </span><span style="color: black;">Index(0, </span><span style="color: blue;">false</span><span style="color: black;">), </span><span style="color: blue;">new </span><span style="color: black;">Index(0, </span><span style="color: blue;">true</span><span style="color: black;">));

</span><span style="color: green;">// Other members.

}

C# 8.0 introduces the index and range syntax:

Index index1 = 1; // Index 1 from start.
Index index2 = ^2; // Index 2 from end.
Range range1 = 1..10; // Start index is 1 from start, end index is 10 from start.
Range range2 = 10..^5; // Start index is 1 from start, end index is 5 from end.
Range range3 = ^10..; // Start index is 10 from end, end index is 0 from end.
Range range4 = ..; // Start index is 0 from start, end index is 0 from end.

These are syntactic sugars, which are compiled to:

Index index3 = 1;
Index index2 = new Index(2, true);
Range range5 = Range.Create(1, 10);
Range range4 = Range.Create(10, new Index(5, true));
Range range3 = Range.FromStart(new Index(10, true));
Range range2 = Range.All();

Index and Range for array

C# introduces syntactic sugars to enable Index with array:

int[] array = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int value = array[^1];

It is compiled to normal int indexer access:

int[] array = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Index index = new Index(1, true);
int value = index.FromEnd ? array[array.Length - index.Value] : array[index.Value];

And this is the range syntactic sugar for array slice:

int[] array = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int[] slice = array[^9..7];

It is compiled to array copy:

int[] array = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Range range = Range.Create(new Index(9, true), 7);
int startIndex = range.Start.FromEnd ? array.Length - range.Start.Value : range.Start.Value;
int rangeLength = (range.End.FromEnd ? array.Length - range.End.Value : range.End.Value) - startIndex;
int[] slice = new int[rangeLength];
Array.Copy(sourceArray: array, sourceIndex: startIndex, destinationArray: slice, destinationIndex: 0, length: rangeLength);

LINQ queries - Index and Range for IEnumerable<T>

Currently (v3.0.0-preview2/SDK 3.0.100-preview-010184), the index and range work with array, and do not work with other types, like List<T>. It is natural and convenient to support index and range in LINQ, so they can work with any type that implements IEnumerable<T>. The goals of these LINQ APIs are:

  • Use index to locate an element in sequence, use range to slice sequence. The usage is the same as index/range for array, but with deferred execution for slice with range.
  • Use range to start fluent LINQ query.

This enables the index and range to work with any type that implements IEnumerable<T>.

LINQ already has ElementAt(int index) and ElementOrDefault(int index) query operator. It would be natural to have a overload for System.Index: ElementAt(Index index) and ElementOrDefault(Index index), and a new method ElementsIn(Range range), so that LINQ can seamlessly work with C# 8.0:

Index index = ...;
var element1 = source1.ElementAt(index);
var element2 = source2.ElementAtOrDefault(^ 5);
Range range = ...;
var slice1 = source3.ElementsIn(range);
var slice2 = source4.ElementsIn(2..^ 2)
var slice2 = source5.ElementsIn(^ 10..);

The following Range overload and AsEnumerable overload for System.Range convert it to a sequence, so that LINQ query can be started fluently from c# range:

Index index = ...;
var element1 = source1.ElementAt(index);
var element2 = source2.ElementAtOrDefault(^ 5);
Range range = ...;
var slice1 = source3.ElementsIn(range);
var slice2 = source4.ElementsIn(2..^ 2)
var slice2 = source5.ElementsIn(^ 10..);

APIs

For LINQ to Objects, ideally:

namespace System.Linq
{
    public static partial class Queryable
    {
        public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, Index index) { throw null; }
    </span><span style="color: blue;">public static </span><span style="color: black;">TSource ElementAtOrDefault&lt;</span><span style="color: rgb(43, 145, 175);">TSource</span><span style="color: black;">&gt;(</span><span style="color: blue;">this </span><span style="color: black;">IEnumerable&lt;TSource&gt; source, Index index) { </span><span style="color: blue;">throw null</span><span style="color: black;">; }

    </span><span style="color: blue;">public static </span><span style="color: black;">IEnumerable&lt;TSource&gt; ElementsIn&lt;</span><span style="color: rgb(43, 145, 175);">TSource</span><span style="color: black;">&gt;(</span><span style="color: blue;">this </span><span style="color: black;">IEnumerable&lt;TSource&gt; source, Range range) { </span><span style="color: blue;">throw null</span><span style="color: black;">; }

    </span><span style="color: blue;">public static </span><span style="color: black;">IEnumerable&lt;TSource&gt; Range&lt;</span><span style="color: rgb(43, 145, 175);">TSource</span><span style="color: black;">&gt;(Range range) { </span><span style="color: blue;">throw null</span><span style="color: black;">; }

    </span><span style="color: blue;">public static </span><span style="color: black;">IEnumerable&lt;TSource&gt; AsEnumerable&lt;</span><span style="color: rgb(43, 145, 175);">TSource</span><span style="color: black;">&gt;(</span><span style="color: blue;">this </span><span style="color: black;">Range source) { </span><span style="color: blue;">throw null</span><span style="color: black;">; }
}

}

For remote LINQ, ideally:

namespace System.Linq
{
    public static partial class Queryable
    {
        public static TSource ElementAt<TSource>(this IQueryable<TSource> source, Index index) { throw null; }

        public static TSource ElementAtOrDefault<TSource>(this IQueryable<TSource> source, Index index) { throw null; }

        public static IQueryable<TSource> ElementsIn<TSource>(this IQueryable<TSource> source, Range range) { throw null; }
    }
}

Implementation details

These APIs' implementation is self-contained so that the code can be just copied to use.

The implementation of ElementAt(Index), ElementOrDefault(Index) and ElementsIn(Range) for IQueryable<T> is straightforward. They just create an expression tree.

internal static class QueryableExtensions
{
    public static TSource ElementAt<TSource>(this IQueryable<TSource> source, Index index)
    {
        if (source == null)
            // throw Error.ArgumentNull(nameof(source));
            throw new ArgumentNullException(nameof(source));
        return source.Provider.Execute<TSource>(
            Expression.Call(
                null,
                CachedReflectionInfo.ElementAt_TSource_2(typeof(TSource)),
                source.Expression, Expression.Constant(index)
                ));
    }
</span><span style="color: blue;">public static </span><span style="color: black;">TSource ElementAtOrDefault&lt;</span><span style="color: rgb(43, 145, 175);">TSource</span><span style="color: black;">&gt;(</span><span style="color: blue;">this </span><span style="color: red;">IQueryable</span><span style="color: black;">&lt;TSource&gt; source, </span><span style="color: red;">Index </span><span style="color: black;">index)
{
    </span><span style="color: blue;">if </span><span style="color: black;">(source == </span><span style="color: blue;">null</span><span style="color: black;">)
        </span><span style="color: green;">// throw Error.ArgumentNull(nameof(source));
        </span><span style="color: blue;">throw new </span><span style="color: red;">ArgumentNullException</span><span style="color: black;">(nameof(source));
    </span><span style="color: blue;">return </span><span style="color: black;">source.Provider.Execute&lt;TSource&gt;(
        </span><span style="color: red;">Expression</span><span style="color: black;">.Call(
            </span><span style="color: blue;">null</span><span style="color: black;">,
            CachedReflectionInfo.ElementAtOrDefault_TSource_2(</span><span style="color: blue;">typeof</span><span style="color: black;">(TSource)),
            source.Expression, </span><span style="color: red;">Expression</span><span style="color: black;">.Constant(index)
            ));
}

</span><span style="color: blue;">public static </span><span style="color: red;">IQueryable</span><span style="color: black;">&lt;TSource&gt; ElementsIn&lt;</span><span style="color: rgb(43, 145, 175);">TSource</span><span style="color: black;">&gt;(</span><span style="color: blue;">this </span><span style="color: red;">IQueryable</span><span style="color: black;">&lt;TSource&gt; source, </span><span style="color: red;">Range </span><span style="color: black;">range)
{
    </span><span style="color: blue;">if </span><span style="color: black;">(source == </span><span style="color: blue;">null</span><span style="color: black;">)
        </span><span style="color: green;">// throw Error.ArgumentNull(nameof(source));
        </span><span style="color: blue;">throw new </span><span style="color: red;">ArgumentNullException</span><span style="color: black;">(nameof(source));

    </span><span style="color: blue;">return </span><span style="color: black;">source.Provider.CreateQuery&lt;TSource&gt;(
        </span><span style="color: red;">Expression</span><span style="color: black;">.Call(
            </span><span style="color: blue;">null</span><span style="color: black;">,
            CachedReflectionInfo.ElementsIn_TSource_2(</span><span style="color: blue;">typeof</span><span style="color: black;">(TSource)),
            source.Expression, </span><span style="color: red;">Expression</span><span style="color: black;">.Constant(range)));
}

}

internal static class CachedReflectionInfo { private static MethodInfo s_ElementAt_TSource_2;

</span><span style="color: blue;">public static </span><span style="color: red;">MethodInfo </span><span style="color: black;">ElementAt_TSource_2(</span><span style="color: red;">Type </span><span style="color: black;">TSource) =&gt;
     (s_ElementAt_TSource_2 ??
     (s_ElementAt_TSource_2 = </span><span style="color: blue;">new </span><span style="color: red;">Func</span><span style="color: black;">&lt;</span><span style="color: red;">IQueryable</span><span style="color: black;">&lt;</span><span style="color: red;">object</span><span style="color: black;">&gt;, </span><span style="color: red;">Index</span><span style="color: black;">, </span><span style="color: red;">object</span><span style="color: black;">&gt;(QueryableExtensions.ElementAt).GetMethodInfo().GetGenericMethodDefinition()))
      .MakeGenericMethod(TSource);

</span><span style="color: blue;">private static </span><span style="color: red;">MethodInfo </span><span style="color: black;">s_ElementAtOrDefault_TSource_2;

</span><span style="color: blue;">public static </span><span style="color: red;">MethodInfo </span><span style="color: black;">ElementAtOrDefault_TSource_2(</span><span style="color: red;">Type </span><span style="color: black;">TSource) =&gt;
     (s_ElementAtOrDefault_TSource_2 ??
     (s_ElementAtOrDefault_TSource_2 = </span><span style="color: blue;">new </span><span style="color: red;">Func</span><span style="color: black;">&lt;</span><span style="color: red;">IQueryable</span><span style="color: black;">&lt;</span><span style="color: red;">object</span><span style="color: black;">&gt;, </span><span style="color: red;">Index</span><span style="color: black;">, </span><span style="color: red;">object</span><span style="color: black;">&gt;(QueryableExtensions.ElementAtOrDefault).GetMethodInfo().GetGenericMethodDefinition()))
      .MakeGenericMethod(TSource);

</span><span style="color: blue;">private static </span><span style="color: red;">MethodInfo </span><span style="color: black;">s_ElementsIn_TSource_2;

</span><span style="color: blue;">public static </span><span style="color: red;">MethodInfo </span><span style="color: black;">ElementsIn_TSource_2(</span><span style="color: red;">Type </span><span style="color: black;">TSource) =&gt;
     (s_ElementsIn_TSource_2 ??
     (s_ElementsIn_TSource_2 = </span><span style="color: blue;">new </span><span style="color: red;">Func</span><span style="color: black;">&lt;</span><span style="color: red;">IQueryable</span><span style="color: black;">&lt;</span><span style="color: red;">object</span><span style="color: black;">&gt;, </span><span style="color: red;">Range</span><span style="color: black;">, </span><span style="color: red;">IQueryable</span><span style="color: black;">&lt;</span><span style="color: red;">object</span><span style="color: black;">&gt;&gt;(QueryableExtensions.ElementsIn).GetMethodInfo().GetGenericMethodDefinition()))
      .MakeGenericMethod(TSource);

}

These methods for IEnumerable<T> are straightforward as well, I just followed the behavior and exceptions of array with range. See unit tests https://github.com/Dixin/CodeSnippets/blob/master/Linq.Range/Linq.Range.Tests/ElementsInTests.cs.

ElementAt(Index) and ElementAtOrDefault(Index):

public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, Index index)
{
if (source == null)
{
// ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
throw new ArgumentNullException(nameof(source));
}

</span><span style="color: blue;">if </span><span style="color: black;">(!index.FromEnd)
{
    </span><span style="color: blue;">return </span><span style="color: black;">source.ElementAt(index.Value);
}

</span><span style="color: red;">int </span><span style="color: black;">indexFromEnd = index.Value;
</span><span style="color: blue;">if </span><span style="color: black;">(indexFromEnd &gt; 0)
{
    </span><span style="color: blue;">if </span><span style="color: black;">(source </span><span style="color: blue;">is </span><span style="color: red;">IList</span><span style="color: black;">&lt;TSource&gt; list)
    {
        </span><span style="color: blue;">return </span><span style="color: black;">list[list.Count - indexFromEnd];
    }

    </span><span style="color: blue;">using </span><span style="color: black;">(</span><span style="color: red;">IEnumerator</span><span style="color: black;">&lt;TSource&gt; e = source.GetEnumerator())
    {
        </span><span style="color: blue;">if </span><span style="color: black;">(e.MoveNext())
        {
            </span><span style="color: red;">Queue</span><span style="color: black;">&lt;TSource&gt; queue = </span><span style="color: blue;">new </span><span style="color: red;">Queue</span><span style="color: black;">&lt;TSource&gt;();
            queue.Enqueue(e.Current);
            </span><span style="color: blue;">while </span><span style="color: black;">(e.MoveNext())
            {
                </span><span style="color: blue;">if </span><span style="color: black;">(queue.Count == indexFromEnd)
                {
                    queue.Dequeue();
                }

                queue.Enqueue(e.Current);
            }

            </span><span style="color: blue;">if </span><span style="color: black;">(queue.Count == indexFromEnd)
            {
                </span><span style="color: blue;">return </span><span style="color: black;">queue.Dequeue();
            }
        }
    }
}

</span><span style="color: green;">// ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
</span><span style="color: blue;">throw new </span><span style="color: red;">ArgumentOutOfRangeException</span><span style="color: black;">(nameof(index));
</span><span style="color: blue;">return default</span><span style="color: black;">!;

}

public static TSource ElementAtOrDefault<TSource>(this IEnumerable<TSource> source, Index index) { if (source == null) { // ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); throw new ArgumentNullException(nameof(source));

}

</span><span style="color: blue;">if </span><span style="color: black;">(!index.FromEnd)
{
    </span><span style="color: blue;">return </span><span style="color: black;">source.ElementAtOrDefault(index.Value);
}

</span><span style="color: red;">int </span><span style="color: black;">indexFromEnd = index.Value;
</span><span style="color: blue;">if </span><span style="color: black;">(indexFromEnd &gt; 0)
{
    </span><span style="color: blue;">if </span><span style="color: black;">(source </span><span style="color: blue;">is </span><span style="color: red;">IList</span><span style="color: black;">&lt;TSource&gt; list)
    {
        </span><span style="color: red;">int </span><span style="color: black;">count = list.Count;
        </span><span style="color: blue;">if </span><span style="color: black;">(count &gt;= indexFromEnd)
        {
            </span><span style="color: blue;">return </span><span style="color: black;">list[count - indexFromEnd];
        }
    }

    </span><span style="color: blue;">using </span><span style="color: black;">(</span><span style="color: red;">IEnumerator</span><span style="color: black;">&lt;TSource&gt; e = source.GetEnumerator())
    {
        </span><span style="color: blue;">if </span><span style="color: black;">(e.MoveNext())
        {
            </span><span style="color: red;">Queue</span><span style="color: black;">&lt;TSource&gt; queue = </span><span style="color: blue;">new </span><span style="color: red;">Queue</span><span style="color: black;">&lt;TSource&gt;();
            queue.Enqueue(e.Current);
            </span><span style="color: blue;">while </span><span style="color: black;">(e.MoveNext())
            {
                </span><span style="color: blue;">if </span><span style="color: black;">(queue.Count == indexFromEnd)
                {
                    queue.Dequeue();
                }

                queue.Enqueue(e.Current);
            }

            </span><span style="color: blue;">if </span><span style="color: black;">(queue.Count == indexFromEnd)
            {
                </span><span style="color: blue;">return </span><span style="color: black;">queue.Dequeue();
            }
        }
    }
}

</span><span style="color: blue;">return default</span><span style="color: black;">!;

}

ElementsIn(Range):

public static IEnumerable<TSource> ElementsIn<TSource>(this IEnumerable<TSource> source, Range range)
{
if (source == null)
{
// ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
throw new ArgumentNullException(nameof(source));
}

</span><span style="color: blue;">return </span><span style="color: black;">ElementsInIterator(source, range);

}

private static IEnumerable<TSource> ElementsInIterator<TSource>(IEnumerable<TSource> source, Range range) { Index start = range.Start; Index end = range.End;

</span><span style="color: blue;">if </span><span style="color: black;">(source </span><span style="color: blue;">is </span><span style="color: red;">IList</span><span style="color: black;">&lt;TSource&gt; list)
{
    </span><span style="color: red;">int </span><span style="color: black;">count = list.Count;
    </span><span style="color: blue;">if </span><span style="color: black;">(count == 0 &amp;&amp; range.Equals(System.</span><span style="color: red;">Range</span><span style="color: black;">.All()))
    {
        </span><span style="color: blue;">yield break</span><span style="color: black;">;
    }

    </span><span style="color: red;">int </span><span style="color: black;">firstIndex = start.FromEnd ? count - start.Value : start.Value;
    </span><span style="color: red;">int </span><span style="color: black;">lastIndex = (end.FromEnd ? count - end.Value : end.Value) - 1;
    </span><span style="color: blue;">if </span><span style="color: black;">(lastIndex &lt; firstIndex - 1)
    {
        </span><span style="color: green;">// ThrowHelper.ThrowOverflowException();
        </span><span style="color: blue;">throw new </span><span style="color: red;">OverflowException</span><span style="color: black;">(); </span><span style="color: green;">// Following the behavior of array with range.
    </span><span style="color: black;">}

    </span><span style="color: blue;">if </span><span style="color: black;">(firstIndex &lt; 0 || lastIndex &lt; 0)
    {
        </span><span style="color: green;">// ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.range);
        </span><span style="color: blue;">throw new </span><span style="color: red;">ArgumentOutOfRangeException</span><span style="color: black;">(nameof(range)); </span><span style="color: green;">// Following the behavior of array with range.
    </span><span style="color: black;">}

    </span><span style="color: blue;">if </span><span style="color: black;">(firstIndex &gt;= count || lastIndex &gt;= count)
    {
        </span><span style="color: green;">// ThrowHelper.ThrowArgumentException(ExceptionArgument.range);
        </span><span style="color: blue;">throw new </span><span style="color: red;">ArgumentException</span><span style="color: black;">(nameof(range)); </span><span style="color: green;">// Following the behavior of array with range.
    </span><span style="color: black;">}

    </span><span style="color: blue;">for </span><span style="color: black;">(</span><span style="color: red;">int </span><span style="color: black;">currentIndex = firstIndex; currentIndex &lt;= lastIndex; currentIndex++)
    {
        </span><span style="color: blue;">yield return </span><span style="color: black;">list[currentIndex];
    }
    </span><span style="color: blue;">yield break</span><span style="color: black;">;
}

</span><span style="color: blue;">using </span><span style="color: black;">(</span><span style="color: red;">IEnumerator</span><span style="color: black;">&lt;TSource&gt; e = source.GetEnumerator())
{
    </span><span style="color: red;">int </span><span style="color: black;">currentIndex = -1;
    </span><span style="color: blue;">if </span><span style="color: black;">(start.FromEnd)
    {
        </span><span style="color: blue;">if </span><span style="color: black;">(!e.MoveNext())
        {
            </span><span style="color: blue;">const </span><span style="color: red;">int </span><span style="color: black;">count = 0;
            </span><span style="color: red;">int </span><span style="color: black;">firstIndex = count - start.Value;
            </span><span style="color: red;">int </span><span style="color: black;">lastIndex = (end.FromEnd ? count - end.Value : end.Value) - 1;
            </span><span style="color: blue;">if </span><span style="color: black;">(lastIndex &lt; firstIndex - 1)
            {
                </span><span style="color: green;">// ThrowHelper.ThrowOverflowException();
                </span><span style="color: blue;">throw new </span><span style="color: red;">OverflowException</span><span style="color: black;">(); </span><span style="color: green;">// Following the behavior of array with range.
            </span><span style="color: black;">}

            </span><span style="color: green;">// ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.range);
            </span><span style="color: blue;">throw new </span><span style="color: red;">ArgumentOutOfRangeException</span><span style="color: black;">(nameof(range));
        }
        </span><span style="color: blue;">else
        </span><span style="color: black;">{
            </span><span style="color: red;">Queue</span><span style="color: black;">&lt;TSource&gt; queue = </span><span style="color: blue;">new </span><span style="color: red;">Queue</span><span style="color: black;">&lt;TSource&gt;();
            queue.Enqueue(e.Current);
            currentIndex++;

            </span><span style="color: red;">int </span><span style="color: black;">takeLastCount = start.Value;
            </span><span style="color: blue;">while </span><span style="color: black;">(e.MoveNext())
            {
                </span><span style="color: blue;">if </span><span style="color: black;">(queue.Count == takeLastCount)
                {
                    queue.Dequeue();
                }

                queue.Enqueue(e.Current);
                currentIndex++;
            }

            </span><span style="color: blue;">if </span><span style="color: black;">(queue.Count &lt; takeLastCount)
            {
                </span><span style="color: green;">// ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.range);
                </span><span style="color: blue;">throw new </span><span style="color: red;">ArgumentOutOfRangeException</span><span style="color: black;">(nameof(range));
            }

            </span><span style="color: red;">int </span><span style="color: black;">firstIndex = currentIndex + 1 - takeLastCount;
            </span><span style="color: red;">int </span><span style="color: black;">lastIndex = end.FromEnd ? currentIndex - end.Value : end.Value - 1;
            </span><span style="color: blue;">if </span><span style="color: black;">(lastIndex &lt; firstIndex - 1)
            {
                </span><span style="color: green;">// ThrowHelper.ThrowOverflowException();
                </span><span style="color: blue;">throw new </span><span style="color: red;">OverflowException</span><span style="color: black;">(); </span><span style="color: green;">// Following the behavior of array with range.
            </span><span style="color: black;">}

            </span><span style="color: blue;">for </span><span style="color: black;">(</span><span style="color: red;">int </span><span style="color: black;">index = firstIndex; index &lt;= lastIndex; index++)
            {
                </span><span style="color: blue;">yield return </span><span style="color: black;">queue.Dequeue();
            }
        }
    }
    </span><span style="color: blue;">else
    </span><span style="color: black;">{
        </span><span style="color: red;">int </span><span style="color: black;">firstIndex = start.Value;
        </span><span style="color: blue;">if </span><span style="color: black;">(!e.MoveNext())
        {
            </span><span style="color: blue;">if </span><span style="color: black;">(range.Equals(System.</span><span style="color: red;">Range</span><span style="color: black;">.All()))
            {
                </span><span style="color: blue;">yield break</span><span style="color: black;">;
            }

            </span><span style="color: blue;">const </span><span style="color: red;">int </span><span style="color: black;">count = 0;
            </span><span style="color: red;">int </span><span style="color: black;">lastIndex = (end.FromEnd ? count - end.Value : end.Value) - 1;
            </span><span style="color: blue;">if </span><span style="color: black;">(lastIndex &lt; firstIndex - 1)
            {
                </span><span style="color: green;">// ThrowHelper.ThrowOverflowException();
                </span><span style="color: blue;">throw new </span><span style="color: red;">OverflowException</span><span style="color: black;">(); </span><span style="color: green;">// Following the behavior of array with range.
            </span><span style="color: black;">}
            </span><span style="color: green;">// ThrowHelper.ThrowArgumentException(ExceptionArgument.range);
            </span><span style="color: blue;">throw new </span><span style="color: red;">ArgumentException</span><span style="color: black;">(nameof(range)); </span><span style="color: green;">// Following the behavior of array with range.
        </span><span style="color: black;">}

        currentIndex++;
        </span><span style="color: blue;">while </span><span style="color: black;">(currentIndex &lt; firstIndex &amp;&amp; e.MoveNext())
        {
            currentIndex++;
        }

        </span><span style="color: blue;">if </span><span style="color: black;">(currentIndex != firstIndex)
        {
            </span><span style="color: green;">// ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.range);
            </span><span style="color: blue;">throw new </span><span style="color: red;">ArgumentOutOfRangeException</span><span style="color: black;">(nameof(range));
        }

        </span><span style="color: blue;">if </span><span style="color: black;">(end.FromEnd)
        {
            </span><span style="color: red;">int </span><span style="color: black;">skipLastCount = end.Value;
            </span><span style="color: blue;">if </span><span style="color: black;">(skipLastCount &gt; 0)
            {
                </span><span style="color: red;">Queue</span><span style="color: black;">&lt;TSource&gt; queue = </span><span style="color: blue;">new </span><span style="color: red;">Queue</span><span style="color: black;">&lt;TSource&gt;();
                </span><span style="color: blue;">do
                </span><span style="color: black;">{
                    </span><span style="color: blue;">if </span><span style="color: black;">(queue.Count == skipLastCount)
                    {
                        </span><span style="color: blue;">yield return </span><span style="color: black;">queue.Dequeue();
                    }

                    queue.Enqueue(e.Current);
                    currentIndex++;
                }
                </span><span style="color: blue;">while </span><span style="color: black;">(e.MoveNext());
            }
            </span><span style="color: blue;">else
            </span><span style="color: black;">{
                </span><span style="color: blue;">do
                </span><span style="color: black;">{
                    </span><span style="color: blue;">yield return </span><span style="color: black;">e.Current;
                    currentIndex++;
                }
                </span><span style="color: blue;">while </span><span style="color: black;">(e.MoveNext());
            }

            </span><span style="color: blue;">if </span><span style="color: black;">(firstIndex + skipLastCount &gt; currentIndex)
            {
                </span><span style="color: green;">// ThrowHelper.ThrowOverflowException();
                </span><span style="color: blue;">throw new </span><span style="color: red;">OverflowException</span><span style="color: black;">(); </span><span style="color: green;">// Following the behavior of array with range.
            </span><span style="color: black;">}
        }
        </span><span style="color: blue;">else
        </span><span style="color: black;">{
            </span><span style="color: red;">int </span><span style="color: black;">lastIndex = end.Value - 1;
            </span><span style="color: blue;">if </span><span style="color: black;">(lastIndex &lt; firstIndex - 1)
            {
                </span><span style="color: green;">// ThrowHelper.ThrowOverflowException();
                </span><span style="color: blue;">throw new </span><span style="color: red;">OverflowException</span><span style="color: black;">(); </span><span style="color: green;">// Following the behavior of array with range.
            </span><span style="color: black;">}

            </span><span style="color: blue;">if </span><span style="color: black;">(lastIndex == firstIndex - 1)
            {
                </span><span style="color: blue;">yield break</span><span style="color: black;">;
            }

            </span><span style="color: blue;">yield return </span><span style="color: black;">e.Current;
            </span><span style="color: blue;">while </span><span style="color: black;">(currentIndex &lt; lastIndex &amp;&amp; e.MoveNext())
            {
                currentIndex++;
                </span><span style="color: blue;">yield return </span><span style="color: black;">e.Current;
            }

            </span><span style="color: blue;">if </span><span style="color: black;">(currentIndex != lastIndex)
            {
                </span><span style="color: green;">// ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.range);
                </span><span style="color: blue;">throw new </span><span style="color: red;">ArgumentOutOfRangeException</span><span style="color: black;">(nameof(range));
            }
        }
    }
}

}

For Range(Range) and AsEnumerable(Range), the question is: what does Range's start Index and end Index mean when the index is from the end? For example, 10..20 can be easily converted to a sequence of 10, 11,12, ... 19, but how about ^20...^10? In my current implementation, regarding Index's value can be from 0 to int.MaxValue, I assume a virtual "full range" 0..2147483648, and any Range instance is a slice of that "full range". So:

  • Ranges .. and 0.. and ..^0 and 0..^0 are converted to "full sequence" 0, 1, .. 2147483647
  • Range 100..^47 is converted to sequence 100, 101, .. 2147483600
  • Range ^48..^40 is converted to sequence 2147483600, 2147483601 .. 2147483607
  • Range 10..10 is converted to empty sequence

etc.

public static IEnumerable<int> Range(Range range)
{
    Index startIndex = range.Start;
    Index endIndex = range.End;
    int firstValue = startIndex.FromEnd ? int.MaxValue - startIndex.Value + 1 : startIndex.Value;
    int lastValue = endIndex.FromEnd ? int.MaxValue - endIndex.Value : endIndex.Value - 1;
    if (lastValue < firstValue - 1)
    {
        // ThrowHelper.ThrowOverflowException();
        throw new OverflowException(); // Following the behavior of array with range.
    }
</span><span style="color: blue;">if </span><span style="color: black;">(lastValue == firstValue - 1)
{
    </span><span style="color: blue;">return </span><span style="color: red;">Enumerable</span><span style="color: black;">.Empty&lt;</span><span style="color: red;">int</span><span style="color: black;">&gt;();
}

</span><span style="color: blue;">return </span><span style="color: black;">RangeIterator(firstValue, lastValue);

}

private static IEnumerable<int> RangeIterator(int firstValue, int lastValue) { for (int value = firstValue; value <= lastValue; value = checked(value + 1)) { yield return value; if (value == int.MaxValue) { yield break; } } }

public static IEnumerable<int> AsEnumerable(this Range range) { int[] array = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int[] slice = array[^ 9..7]; return Range(range); }

See unit tests of AsEnumerable(Range) https://github.com/Dixin/CodeSnippets/blob/master/Linq.Range/Linq.Range.Tests/AsEnumerableTests.cs.

2 Comments

  • I have the following code:

    var accidents = text.Skip(NumberOfAccidentsLine + 1).Take(numberOfAccidentsInFile).ToArray();
    where accidents is an array of strings.

    I want to make a Linq transformation from the string array to an array of Accident objects as follows:

    return accidents.Select(t => new Accident() {Id = i, Name = t.Replace("\"", string.Empty)}).ToArray();
    How do I retrieve the index i from the accidents array using Linq or do I have to go old school?

    #c# #linq #ienumerable

  • This is a great article on understanding the new features of C# 8 and LINQ. I also found a great article on <a href="https://systemoutofmemory.com/blogs/the-programmer-blog/c-sharp-iterate-dictionary">C# iterate dictionary which explains how to iterate dictionaries using LINQ. Let me know what you think.

    Vihaan

Add a Comment

As it will appear on the website

Not displayed

Your website