NDepend Blog

Improve your .NET code quality with NDepend

C#-index-and-range-operators

C# Index and Range Operators Explained

December 11, 2023 3 minutes read

In this post, we are demystifying both the C# index ^ and range .. operators most comprehensively.

The index operator ^

Let’s start with the index ^ operator:

If you are used to regular expression (regex) this syntax is a bit misleading. In C# the ^ operator means index-from-the-end while in regex the character ^ matches the starting position within the string.

The range operator ..

The range operator .. is used to make a slice of a collection.

Keep in mind that:

  • start of the range is inclusive
  • end of the range is exclusive

If you’ve been studied mathematic this syntax is a bit misleading. The C# syntax [1..4] translates to this math notation[1..4[.

Mixing index and range operators

Both operators can be mixed within the same expression:

What’s behind the index ^ operator syntactic sugar?

Actually, the C# compiler translates these operators to the structures System.Index and System.Range. These structures were introduced with .NET Core 3.0 in 2019. These structures are also provided by .NET Standard 2.1 but not .NET Standard 2.0. It means that you cannot use this syntax within your .NET Framework projects.

Here is what the compiler does for the index syntax:

In simple situations, the C# compiler doesn’t need the Index structure and can do the indexing-from-the-end work in IL.

CSharp Compiler Optimization with Index
CSharp Compiler Optimization with Index

What’s behind the range .. operator syntactic sugar?

We already mentioned the structure Range. Here is how it is used by the compiler. Notice the call to the special method T[] System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray<T>(T[] array, Range range).

Support for collections other than array

The index syntax ^ works for all collection types that have both:

  • a Count or Length property,
  • and a single integer indexer [int].

Index and Range support for mainstream collections

As we can see the index syntax ^ works with IList<T> and List<T> but not with ISet<T>, Hashset<T>, IDictionary<K,V> and Dictionary<K,V>. Those last four are not indexed collections.

The range syntax .. is more restrictive and also requires the collection type to present an int[] Slice(int start, int length) method. Because of this, the range operator is not working with any of these collections. Not even with IList<T> and List<T> as one might have anticipated.

Adding a Slice() method to IList<T> and List<T>collections was not an option because in many situations it is a breaking change. Here is a reddit discussion about attempting to palliate this lack. It looks like adding a Slice() method to IList<T> with the default interface implementation syntax could have been an option that would have avoided breaking changes. I didn’t find the reason why it hasn’t been done, do you know?

Support for collections other than array

Finally here is an example of a custom collection supporting both index and range syntax:

Conclusion

Personally, I need to thoroughly understand syntactic sugar before using it, hence this post.

This new syntax is great but a bit limited because IList<T> and List<T> don’t support range.

Comments:

  1. Patrick Tingen says:

    What makes it confusing for me is that c# is 0-based, while the index operator works 1-based. I come from a language that is fully 1-based so, working with a 0-based language requires some extra brainpower. Why is the last element in an array:

    Assert.IsTrue(arr[^1] == 5);

    Instead of

    Assert.IsTrue(arr[^0] == 5);

  2. Thanks for this explanation,
    I miss some examples that might be instructive about the scope of the operators.

    1. can I use the ^ index with a variable ?

    x = 3;
    y = arr[x]; ==> y = 3
    z = arr[^x]; ==> y = ?

    2. does the range arr[3..^3] include or exclude element 3?

    3. how will this go wrong arr[^4..3] or arr[^8] ==> IndexOutOfRangeException

    4. and definitely wrong arr[^-2] ==> IndexOutOfRangeException ?

    Regards,
    Rob

  3. George Washington says:

    Handy ref – thanks. One bug spotted; You refer to IList type, but the code shown actually creates a List.

    This is actually a good example of why it is often a good idea to declare the types of your vars 🙂

Comments are closed.