C# 8.0 introduced the index operator ^ and range operator ... The index operator lets you access elements relative to the end of a collection: array[^1] is the last element, array[^2] is second-to-last. The range operator creates slices: array[2..5] extracts elements from index 2 to 4 (5 is exclusive). Both work on arrays, strings, and any type implementing IEnumerable or supporting indexing.
Why it matters
These operators eliminate manual offset arithmetic. Before, getting the last element required array[array.Length - 1]; now it’s simply array[^1]. Ranges replace tedious Array.Copy() or Substring() calls. They make algorithms more expressive and less error-prone, especially when working with sequences, text parsing, or pagination logic.
Practical examples
Common patterns:
var numbers = new[] { 1, 2, 3, 4, 5 };
// Index from the end
var last = numbers[^1]; // 5
var secondToLast = numbers[^2]; // 4
// Range slicing
var middleThree = numbers[1..4]; // [2, 3, 4]
var firstThree = numbers[..3]; // [1, 2, 3]
var lastThree = numbers[^3..]; // [3, 4, 5]
// Works on strings
string text = "Hello, World!";
string lastWord = text[^6..]; // "World!"
// Pagination
int pageSize = 10;
var page = items[pageIndex * pageSize..(pageIndex + 1) * pageSize];
Cautions
Range and index operators support arrays, strings, and Span<T>, but not all collections (e.g., List<T> requires System.Collections.Generic.CollectionsMarshal.AsSpan() conversion first). An out-of-bounds index like array[^100] throws IndexOutOfRangeException. Ranges are exclusive at the end, so [0..5] includes indices 0–4 but not 5.
Index operator (^)
Access array elements from the end using the ^ operator.
Valid since C# 8.0
// Before: Manual offset arithmetic
public class ArrayManipulationOld
{
public int GetLastElement(int[] numbers)
{
return numbers[numbers.Length - 1];
}
public int[] GetLastThreeElements(int[] numbers)
{
int start = numbers.Length - 3;
int[] result = new int[3];
System.Array.Copy(numbers, start, result, 0, 3);
return result;
}
public string ExtractDomain(string email)
{
int atIndex = email.IndexOf('@');
return email.Substring(atIndex + 1);
}
}
// After: Index and range operators
public class ArrayManipulationNew
{
public int GetLastElement(int[] numbers)
{
return numbers[^1];
}
public int[] GetLastThreeElements(int[] numbers)
{
return numbers[^3..];
}
public string ExtractDomain(string email)
{
int atIndex = email.IndexOf('@');
return email[(atIndex + 1)..];
}
}
Range operator (..)
Extract contiguous subsequences from arrays and strings.
Valid since C# 8.0
public class RangeOperatorExamples
{
public void ArrayRanges()
{
var numbers = new[] { 10, 20, 30, 40, 50, 60, 70, 80 };
// Range from start
int[] firstThree = numbers[..3]; // [10, 20, 30]
// Range to end
int[] lastThree = numbers[5..]; // [60, 70, 80]
// Range in middle
int[] middle = numbers[2..5]; // [30, 40, 50]
// Full range (creates a copy)
int[] copy = numbers[..]; // all elements
}
public void StringRanges()
{
string path = "C:\\Users\\Documents\\file.txt";
// Extract filename
int lastSlash = path.LastIndexOf('\\');
string fileName = path[(lastSlash + 1)..]; // "file.txt"
// Extract extension
int lastDot = path.LastIndexOf('.');
string extension = path[lastDot..]; // ".txt"
// Extract all but extension
string nameOnly = path[..lastDot]; // "C:\\Users\\Documents\\file"
}
public void SpanRanges()
{
Span<byte> buffer = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
// Extract header (first 2 bytes)
Span<byte> header = buffer[..2];
// Extract payload (skip first 2 bytes)
Span<byte> payload = buffer[2..];
// Extract specific frame (zero-copy operation)
Span<byte> frame = buffer[1..4];
}
}
Combining range and index operators
Use range and index operators together for powerful sequence manipulation.
Valid since C# 8.0
public class CombinedRangeAndIndex
{
public void PaginationExample()
{
var allItems = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
int pageSize = 3;
int pageNumber = 1; // 0-indexed
// Extract one page using range
var page = allItems[(pageNumber * pageSize)..((pageNumber + 1) * pageSize)];
// page = [4, 5, 6]
}
public void LogRotationExample()
{
var logLines = new[]
{
"[INFO] App started",
"[DEBUG] Loading config",
"[INFO] Config loaded",
"[WARN] Cache miss",
"[ERROR] Connection failed",
"[INFO] Retry attempt 1"
};
// Get all lines except the first and last (trim headers and summary)
var mainContent = logLines[1..^1];
// Get last 3 lines (recent activity)
var recent = logLines[^3..];
// Get errors and warnings (assuming they're at specific indices)
var criticalLines = logLines[4..5]; // Just the error
}
public void DataValidationExample()
{
string csvLine = "123,John,Doe,john@example.com,2025-01-15";
var fields = csvLine.Split(',');
// Extract required fields using index from end
string email = fields[^2]; // "john@example.com"
string dateStr = fields[^1]; // "2025-01-15"
// Extract name parts (first two fields after ID)
string fullName = string.Join(" ", fields[1..3]); // "John Doe"
// Validate: must have at least 5 fields
bool valid = fields.Length >= 5;
}
public void SlidingWindowExample()
{
var values = new[] { 10, 20, 30, 40, 50, 60, 70, 80 };
int windowSize = 3;
// Calculate moving average
double[] movingAverages = new double[values.Length - windowSize + 1];
for (int i = 0; i < movingAverages.Length; i++)
{
var window = values[i..(i + windowSize)];
movingAverages[i] = window.Average();
}
// movingAverages[0] = 20 (avg of 10, 20, 30)
// movingAverages[1] = 30 (avg of 20, 30, 40)
}
}