5 min read

Range and Indices Using C#

Can you bypass the for loop entirely by condensing it and getting the sections of an array you are only interested in? These are where the range and indices come into the picture.
Rocca Brancaleone, Ravenna, entrata
Photo by Elisa Amadori / Unsplash

Introduction and Background

Before C# 8.0, when developers were looping through an array, they usually used a variable name or a number literal inside square brackets to indicate the index position of a collection.

To give you an example about it. Let's see the code below.

var programmingLanguages = 
		new string[] { "C#", "F#", "VB.NET", "PHP"};

var programmingLanguageLength = programmingLanguages.Length;

for (var counter = 0; counter < programmingLanguageLength; counter++)
{
    //access index via a variable
    Console.WriteLine(programmingLanguages[counter]);
}
//access via index using number literal;
Console.WriteLine(programmingLanguages[0]);
Console.WriteLine(programmingLanguages[1]);

Relatively easy to understand for most developers. But what if you can bypass the for loop entirely by condensing it and getting the sections of an array you are only interested in? These are where the range and indices come into the picture.

Table Of Contents

What are Indices?

We can say that indices help developers identify an item's index within a specific array or collection and a range of items using two indexes.

You might think we can do that already by declaring a variable with an integer type and passing that into the square brackets to access that specific index.

int index3 = 3;

var programmingLanguages = 
            new string[] { "C#", "F#", "VB.NET", "PHP"};

var programmingLanguage = programmingLanguages[index3];

Let's think about Index as a formal way of identifying a position, and not only that, it also supports counting from the end of a collection.

Examples

Before we start with the code sample, I would say that we'll be using the array below for the rest of the code samples.

private readonly string[] _carCollections = 
		new string[] 
        { "Mazda", "Toyota", "Suzuki", "BMW", "Honda", "Ford" };

OK, this time, let's see how to declare an Index that will start from the 2nd index.

[Fact]
public void Test_Indices_That_Counts_From_Start()
{
    var index = new Index(value: 2);
    var result = _carCollections[index];
    Assert.Equal("Suzuki", result);
}

Suppose you wonder if there's an implicit way to do it. Yes, there's one. Let's see the example below.

[Fact]
public void Test_Indices_That_Counts_From_Start_Using_Implicit_Conversion()
{
    Index index = 2;
    var result = _carCollections[index];
    Assert.Equal("Suzuki", result);
}

OK, seems convincing, right? How about the index that starts counting at the end of a collection?

Let's see an example below.

[Fact]
public void Test_Indices_That_Counts_From_The_End()
{
    var index = new Index(value: 2, fromEnd: true);
    var result = _carCollections[index];
    Assert.Equal("Honda", result);
}

How about using the hat or caret operator this time?

 [Fact]
 public void Test_Indices_That_Counts_From_The_End_Using_Caret_Operator()
 {
     var index = ^2;
     var result = _carCollections[index];
     Assert.Equal("Honda", result);
 }

In our example, when we encounter the ^2 character, it is equivalent to _carCollections.Length-2.

When we use the index from the end, the value should be greater than zero. Else, a System.IndexOutOfRangeException will be thrown. Let's see an example below.

[Fact]
public void Test_Indices_Throw_IndexOUtOfRangeException()
{
    var index = ^0;
    Assert.Throws<IndexOutOfRangeException>(()=> _carCollections[index]);
}

Syntax Summary

To summarize everything about indices, here's a good cheat code sample.

// two ways to define the same index, 2 in from the start
Index x = new(value: 2); // counts from the start
Index y = 2; // using implicit int conversion operator

// two ways to define the same index, 1 in from the end
Index a = new(value: 1, fromEnd: true);
Index b = ^1; // using the caret operator

What are Ranges?

It is a way of representing a subset( a group) of a sequence using the .. operator. Or we can also use the Range object.

Using .. Operator

Let's try using the .. operator.

How about the full range?

[Fact]
public void Test_FullRange()
{
    var result = new StringBuilder();

    //full range [..]
    foreach (var car in _carCollections[..])
    {
        var output = string.Concat(car, Environment.NewLine);
        this._output.WriteLine(output);
        result.Append(output);
    }

    var expected = $"{string.Concat("Mazda", Environment.NewLine)}" +
    $"{string.Concat("Toyota", Environment.NewLine)}" +
    $"{string.Concat("Suzuki", Environment.NewLine)}" +
    $"{string.Concat("BMW", Environment.NewLine)}" +
    $"{string.Concat("Honda", Environment.NewLine)}" +
    $"{string.Concat("Ford", Environment.NewLine)}";

    Assert.Equal(expected, result.ToString());
}

As you can see from the example above, we have used the [..]operator. We can also define the start index and last index.

Let's see an example, how about an open-ended range declaration?

[Fact]
public void Test_Access_Start_From_The_3rd_Index()
{
    var selectedCarsOnly = new List<string>();
    //open-ended range declaration
    foreach (var car in _carCollections[3..]) 
    {
        this._output.WriteLine(car);
        selectedCarsOnly.Add(car);
    }

    Assert.True(selectedCarsOnly.Count == 3);

    Assert.Equal("BMW", selectedCarsOnly[0]);
    Assert.Equal("Honda", selectedCarsOnly[1]);
    Assert.Equal("Ford", selectedCarsOnly[2]);
}

As you can see from the example above, we have started at the 3rd index by explicitly stating the 3 which resulted into [3..].

How about this time? We'll declare the start and the last index.

[Fact]
public void Test_Access_First_Three_Elements()
{
    var selectedCarsOnly = new List<string>();

    //start at index zero and end at the 3rd element
    foreach (var car in _carCollections[0..3]) 
    {
        this._output.WriteLine(car);
        selectedCarsOnly.Add(car);
    }

    Assert.True(selectedCarsOnly.Count == 3);
    Assert.Equal("Mazda",selectedCarsOnly[0]);
    Assert.Equal("Toyota", selectedCarsOnly[1]);
    Assert.Equal("Suzuki", selectedCarsOnly[2]);
}

Using Range Object

I know you're probably waiting for how to use the Rangeobject this time. OK, let's try another example using this object.

[Fact]
public void Test_Defining_Ranges_Outside_Brackets()
{
    var range = new Range(start: 1, end: 4);

    var selectedCarsOnly = new List<string>();

    foreach (var car in _carCollections[range]) 
    {
        this._output.WriteLine(car);
        selectedCarsOnly.Add(car);
    }

    Assert.True(selectedCarsOnly.Count == 3);

    Assert.Equal("Toyota", selectedCarsOnly[0]);
    Assert.Equal("Suzuki", selectedCarsOnly[1]);
    Assert.Equal("BMW", selectedCarsOnly[2]);
}

OK, from the example above, it is evident that we have used the Range object via this syntax var range = new Range(start: 1, end: 4);.

How about the ^ character this time? Let's try another.

[Fact]
public void Test_Range_With_Hat_Character()
{
    var range = Range.StartAt(^3);

    var selectedCarsOnly = new List<string>();

    foreach (var car in _carCollections[range]) 
    {
    this._output.WriteLine(car);
    selectedCarsOnly.Add(car);
    }

    Assert.True(selectedCarsOnly.Count == 3);

    Assert.Equal("BMW", selectedCarsOnly[0]);
    Assert.Equal("Honda", selectedCarsOnly[1]);
    Assert.Equal("Ford", selectedCarsOnly[2]);

}

The example above shows how we can start from the 3rd index from the last part of an array.

Syntax Summary

To summarize, everything here's a good cheat code sample.

Range r1 = new(start: new Index(2), end: new Index(9));
Range r2 = new(start: 2, end: 9); // using implicit int conversion
Range r3 = 2..9; // using C# 8.0 or later syntax
Range r4 = Range.StartAt(2); // from index 2 to last index
Range r5 = 2..; // from index 2 to last index
Range r6 = Range.EndAt(2); // from index 0 to index 2
Range r7 = ..2; // from index 0 to index 2

Summary

This post has discussed the ranges and indices, primarily focusing on their syntax and usage within a collection like an array.

I hope you have enjoyed this article. You can also find the sample code here on GitHub. Till next time, happy programming!

References