Range Type in C# 8

Getting Setup With C# 8

If you aren’t sure if you are using C# 8, or you know you aren’t and want to know how to access these features. Read this quick guide on getting setup with .NET Core and C# 8.

Introduction To Ranges

Let’s first just show some code and from there, we can iterate on it trying a few different things.

Our starting code looks like :

static void Main(string[] args)
{
    var myArray = new string[] 
    {
        "Item1",
        "Item2",
        "Item3",
        "Item4", 
        "Item5"
    };

    for(int i=1; i <= 3; i++)
    {
        Console.WriteLine(myArray[i]);
    }

    Console.ReadLine();
}

So essentially we are looking to go from “index” 1 – 3 in our list and output the item. Unsurprisingly when we run this code we get

Item2
Item3
Item4

But let’s say we don’t want to use a for loop, and instead we want to use this new fandangle thing called “ranges”. We could re-write our code like :

static void Main(string[] args)
{
    var myArray = new string[]
    {
        "Item1",
        "Item2",
        "Item3",
        "Item4",
        "Item5"
    };

    foreach (var item in myArray[1..3])
    {
        Console.WriteLine(item);
    }

    Console.ReadLine();
}

And when we run it?

Item2
Item3

Uh oh… We have one less item than we thought. We’ve ran into our first gotcha when using Ranges. The start of a range is inclusive, but the end of a range is exclusive.

If we change our code to :

static void Main(string[] args)
{
    var myArray = new string[]
    {
        "Item1",
        "Item2",
        "Item3",
        "Item4",
        "Item5"
    };

    foreach (var item in myArray[1..4])
    {
        Console.WriteLine(item);
    }

    Console.ReadLine();
}

Then we get the expected result.

Shortcut Ranges

Using Ranges where it’s “From this index to this index” are pretty handy. But what about “From this index to the end”. Well Ranges have you covered!

From An Index (Inclusive) To The End (Inclusive)

static void Main(string[] args)
{
    var myArray = new string[]
    {
        "Item1",
        "Item2",
        "Item3",
        "Item4",
        "Item5"
    };

    foreach (var item in myArray[1..])
    {
        Console.WriteLine(item);
    }

    Console.ReadLine();

}

Output :

Item2
Item3
Item4
Item5

From The Start (Inclusive) To An Index (Exclusive)

foreach (var item in myArray[..3])
{
    Console.WriteLine(item);
}

Output :

Item1
Item2
Item3

Entire Range (Inclusive)

foreach (var item in myArray[..])
{
    Console.WriteLine(item);
}

Output :

Item1
Item2
Item3
Item4
Item5

From Index (Inclusive) To X From The End (Inclusive)

This one deserves a quick description. Essentially we have a new operator ^  that is now used to designate “From the end”. I labelled this one as “Inclusive” but it really depends on how you think about “from the end”. In my mind in a list of 5 items ^1  would refer to the 4th item. So if that’s included in the result, it’s inclusive.

foreach (var item in myArray[1..^1])
{
    Console.WriteLine(item);
}

Output :

Item2
Item3
Item4

Range As A Type

When we write 1..4, it looks like we are dealing a couple of integers in a new special format, but actually this is almost the initialisation syntax for a range. Similar how we might use {“1”, “2”, “3”} to illustrate us creating an array or list.

We can actually rip out the range from here, and create a new type to be passed around.

static void Main(string[] args)
{
    var myArray = new string[]
    {
        "Item1",
        "Item2",
        "Item3",
        "Item4",
        "Item5"
    };

    Range range = 1..4;

    foreach (var item in myArray[range])
    {
        Console.WriteLine(item);
    }

    Console.ReadLine();
}

This could be handy to pass around into a method or re-use multiple times. I think it’s pretty close to using something like the System.Drawing.Point  object to specify X and Y values rather than having two separate values passed around.

A New Substring

One pretty handy thing about ranges is that they can be used as a faster way to doString.Substring operations. For example :

Console.WriteLine("123456789"[1..4]);

Would output 234 . Obviously this would be a slight shift in thinking because string.Substring isn’t “From this index to this Index” but instead “From this index count this many characters”.

Who Uses Arrays Anyway? What About List<T>?

So when I first had a play with this. I couldn’t work out what I was doing wrong… As it turns out. It was because I was using a List<T>  instead of an Array. I don’t to be too presumptuous but I would say the majority of times you see a “list” or “set” or data in a business application, it’s going to be of type List<T> . And well.. It’s not available to use Ranges. I also can’t see any talk of it being made either (Possibly because of the linked list nature of List<T> Apparently List<T> is not a linkedlist at all! So then I’m not sure why Ranges are not available). Drop a comment below on your thoughts!

10 thoughts on “Range Type in C# 8”

  1. Interesting but the part that this does not work on List I do not know, day to day work uses Lists a lot and having this just in Arrays, blah.
    Love C# by the way.
    Thanks for the article.

    Reply
    • Syntactically that may work. But you are incurring a large cost because ToArray actually copies the elements to a new array. You would be better off just using Skip/Take.

      Reply
      • Hi, you can use:
        var slice = myList.AsSpan(1..2);

        This way you are not copying any data at all, and any iteration is done upon this particular range (slice) you have defined, its extreme efficient..

        Reply
  2. static void Main(string[] args)
    {
        var myArray = new string[]
        {
            "Item1",
            "Item2",
            "Item3",
            "Item4",
            "Item5"
        };
     
        foreach (var item in myArray[1..3])
        {
            Console.WriteLine(item);
        }
     
        Console.ReadLine();
    }
    
    
    And when we run it?
    
    Item2
    Item3
    
    Uh oh… We have one less item than we thought. We’ve ran into our first gotcha when using Ranges. The start of a range is inclusive, but the end of a range is exclusive.
    

    So, if it is inclusive then why didn’t it include item1?
    I think this maybe a typo?

    Reply
    • Hi Corey,

      It’s because indexes still start at 0 when talking about ranges. So the items would look like

      Index 0 – Item1
      Index 1 – Item2
      Index 2 – Item3
      Index 3 – Item4

      So when we give a range of 1..3. In simpler terms we are asking for indexes 1 and 2. Does that make sense?

      Reply
  3. The issue with using this with a List is because the size of a list can change where as an array is fixed. So if you create a slice of a List and them clear it that slice would no longer be valid.

    Reply
  4. The syntax used for ranges is terrible, I’ve argued this in the C# language github repository discussion. It could have been simpler, more elegant and fully symmetrical. It could have made inclusivity/exclusivity part of the syntax too. I consider this a degradation of the language and expect it will be seen that way over time.

    Reply
  5. Shortcut range “To The End (Inclusive)” and “To X From The End (Inclusive)” seem inconsistent with the standard “To An Index (Exclusive)”. But we can make them consistent by viewing them differently. These aren’t “To The End” but rather “To The Length”.

    Think of shortcut range `m[1..]` as `m[1..(m.Length)]`, and you see we can characterize this as “From Index (Inclusive) To The Length (Exclusive)”.

    Similarly, think of the ^ operator as “from the length”. So `m[1..]` is the same as `m[1..^0]`, or “to zero from the length (exclusive)”. To use your example, for an array of 5 items, ^0 designates the index off the end, i.e. [5], ^1 designates one before the end, i.e. [4], which is the 5th item (cause 0-based). So here too, `m[..X]` means “From The Start To X From the Length (Exclusive)”.

    Our array indices start at zero, and they terminate when they reach the length (exclusive). Think of the index to the last element (one from the length) in an array as more akin to [1] (one from the start) than to [0].. (How do we traditionally reference the value at that index? `m[m.Length – 1]`) Range preserves the same semantic. HTH.

    Reply

Leave a Comment