Range and index operators

C# 8.0 NETCore 3.0

Published Updated Author Jeffrey T. Fritz Reading time

Range (…) and index (^) operators provide concise syntax for accessing array elements and extracting subsequences. The index operator lets you count from the end; the range operator extracts contiguous slices without manual offset calculations.

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)
    }
}

Learn more

Range operator .. (Microsoft Learn)