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!
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.
Welp, if it doesn’t work with Lists then just use myList.ToArray()[1..2].
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.
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..
So, if it is inclusive then why didn’t it include item1?
I think this maybe a typo?
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?
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.
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.
Here’s the discussion https://github.com/dotnet/csharplang/issues/2140
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.