Arrays are fundamental structures in C#, allowing storage and manipulation of collections of elements of the same type. Starting from C# 8.0, new features have been introduced to make working with arrays even more efficient. In this article, we will explore everything from traditional indices to the latest advancements, such as negative indices using the “^” operator and ranges “(..)” to create subarrays.
Traditional indexes in arrays
In arrays, indices are used to access individual elements. They start from zero, meaning the first element is at index 0, the second at index 1, and so on.
If you attempt to access an index outside the array’s bounds (numbers[5]), an “IndexOutOfRangeException” will be thrown.
Negative indexes with the "^" operator
With C# 8.0, the “^” operator was introduced to work with indices relative to the end of the array. This simplifies accessing the last element or elements close to the end.
Here, the number after the “^” operator indicates the position to be accessed from the back of the array. For instance:
int[] numbers = { 10, 20, 30, 40, 50 };
Console.WriteLine(numbers[^1]);
Console.WriteLine(numbers[^2]);
In the example above, the console will display the values 50 (last element) and 40 (second-to-last element).
Ranges with the ".." operator
The “..” operator allows creating subarrays (slices) concisely. It uses two indices, a start and an end, separated by “..”.
Rules for using the ".." operator
When defining a range as [start..end], you specify where the range begins and ends.
int[] numbers = { 10, 20, 30, 40, 50 };
int[] slice1 = numbers[1..3];
Console.WriteLine(string.Join(", ", slice1));
In this example, the slice includes elements numbers[1] (20) and numbers[2] (30), but excludes numbers[3].
If you omit the start index (..end), the range starts at the first element and goes up to the specified index (not included).
int[] slice2 = numbers[..3];
Console.WriteLine(string.Join(", ", sl
In this case, the range [2..] includes all elements from numbers[2] to the end of the array.
If you omit both the start and end indices (..), the range encompasses the entire array. This syntax is equivalent to creating a complete copy of the array.
int[] slice3 = numbers[2..];
Console.WriteLine(string.Join(", ", slice3));
In this case, the range [2..] includes all elements from numbers[2] to the end of the array.
If you omit both the start and end indices (..), the range encompasses the entire array. This syntax is equivalent to creating a complete copy of the array.
int[] fullSlice = numbers[..];
Console.WriteLine(string.Join(", ", fullSlice));
Combining with the "^" operator
The “^” operator can be combined with “..” to create ranges relative to the end of the array. This is useful for working with the last elements of a collection.
int[] lastElements = numbers[^3..];
Console.WriteLine(string.Join(", ", lastElements));
The range [^3..] starts from the third element from the end and extends to the end of the array. The output will be 30, 40, 50.
int[] withoutLastTwo = numbers[..^2];
Console.WriteLine(string.Join(", ", withoutLastTwo));
The range [ ..^2 ] goes from the beginning of the array up to two elements before the end. The result will be 10, 20, 30.
Combining ranges with extension methods
Another possibility in C# is combining ranges with extension methods like Where, Select, and others available in LINQ. This allows creating filters and dynamic transformations directly on array slices.
You can use the “..” operator to create a subarray and apply a dynamic filter using the Where method:
int[] numbers = { 10, 15, 20, 25, 30 };
var evens = numbers[1..4].Where(n => n % 2 == 0);
Console.WriteLine(string.Join(", ", evens));
The range [1..4] selects elements from index 1 to index 3 (15, 20, 25). Then, the Where method filters even numbers. The output is 20.
Besides filtering, you can transform the values of a range using the Select method:
int[] numbers = { 10, 15, 20, 25, 30 };
var doubled = numbers[2..].Select(n => n * 2);
Console.WriteLine(string.Join(", ", doubled));
In this example, the range [2..] selects all elements from index 2 onward (20, 25, 30). The Select method applies the transformation n * 2 to each element in the range.
The result is a new list with the numbers 40, 50, 60 stored in the doubled variable.
Conclusion
Negative indices and ranges in C# offer new possibilities for manipulating arrays and collections simply and efficiently. These features make the code more expressive, eliminating the need for manual index calculations. Additionally, ranges and negative indices can be combined with other modern C# features to create robust and clear solutions.
Explore these functionalities in your projects and see how they can simplify your implementations!