How to use C# 8 Indices and Ranges in .NET Standard 2.0 and .NET Framework

  • .NET

This post is part of the series 'C# 8'. Be sure to check out the rest of the blog posts of the series!

Index and Range are 2 new types that support the new language features: hat and range operators.

The ^ operator indicates the element position from the end of a sequence. For a sequence of length length, ^n points to the element with offset length - n from the start of a sequence. For example, ^1 points to the last element of a sequence and ^length points to the first element of a sequence.

The .. operator specifies the start and end of a range of indices as its operands. The left-hand operand is an inclusive start of a range. The right-hand operand is an exclusive end of a range. Either of operands can be an index from the start or from the end of a sequence, as the following example shows:

var str = "abcde";
Assert.Equal('e', str[^1]);      // str[str.Length - 1]
Assert.Equal("bcd", str[1..^1]); // str.Substring(1, str.Length - 2)
Assert.Equal("abcd", str[..^1]); // str.Substring(0, str.Length - 1)
Assert.Equal("bcde", str[1..]);  // str.Substring(1)

byte[] array = { 1, 2, 3, 4, 5 };
Assert.Equal(4, array[^2]);

Assert.Equal(new byte[] { 2, 3, 4 }, array[1..^1]);
ReadOnlySpan<byte> span = array.AsSpan();
Assert.Equal((ReadOnlySpan<byte>)new byte[] { 2, 3, 4 }, span[1..^1]);

The proposal explains all the details of these new operators and how the compiler handle them. The most interesting things are what your classes must implement to get benefits from the hat operator or the range operator.

These operators work out of the box with .NET Core 3.0. In this post we'll see how to use the new operators in .NET Standard 2.0 and .NET Framework.

Enable C# 8 in your project

To use C# 8 features you need to enable it in your project. This may already be the case by default, but you can be explicit by editing the csproj and adding <LangVersion>8.0</LangVersion>:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>8.0</LangVersion> <!-- 👈 Enable C# 8 features -->
  </PropertyGroup>
</Project>

Add the requires types

To use Index and Range from .NET Standard 2.0 or .NET Framework, the following types and methods must be declared in your code:

// Implementation available at https://gist.github.com/meziantou/177600eab9961f3296060d1b8bcd5f40
namespace System
{
    public readonly struct Index
    {
        // To use the "hat" operator (^), the following is required:
        public Index(int value, bool fromEnd);

        // To use the System.Index type as an argument in an array element access, the following member is required:
        int GetOffset(int length);
    }

    // The .. syntax for System.Range will require the System.Range type, as well as one or more of the following members:
    public readonly struct Range
    {
        public Range(System.Index start, System.Index end);
        public static Range StartAt(System.Index start);
        public static Range EndAt(System.Index end);
        public static Range All { get; }
    }
}

namespace System.Runtime.CompilerServices
{
    public static class RuntimeHelpers
    {
        // For a value of type System.Range to be used in an array element access expression, the following member must be present:
        public static T[] GetSubArray<T>(T[] array, System.Range range);
    }
}

You can copy the implementation from this Gist: https://gist.github.com/meziantou/177600eab9961f3296060d1b8bcd5f40.

This code is mostly a copy from the CoreFX repository. It needed small changes to compile the code for .NET Standard 2.0. Indeed, the CoreFX's implementation use Span and CacheStringBuilder. This improves the performance of the ToString methods, but this is not very useful in most projects. So, it was replaced with less performant implementations.

Index and Range types don't need to be public as long as there are not part of the public API of your assembly. The types of this Gist are internal because I don't think it's a good thing to expose them and force anyone to use these ones instead of the one from the BCL. Moreover, it won't work well if you use multiple libraries that use these types as public as they would conflict.

And voilà! You can use the hat and range operators in your project 😃

Do you have a question or a suggestion about this post? Contact me on Twitter or by email!

Follow me:
Enjoy this blog?Buy Me A CoffeeDonate with PayPal