Назад Вперед Зміст

Індекси, діапазони та індексатори в C#

Повний посібник із прикладами та практичними завданнями

Індекси в C# 8.0+

Індекси дозволяють звертатися до елементів колекції з кінця, використовуючи оператор ^ (карет).

Основні поняття:

  • ^1 - останній елемент
  • ^2 - передостанній елемент
  • ^0 - межа масиву (еквівалентно array.Length)
  • ^ працює тільки з колекціями, що мають властивість Length або Count
Основи використання індексів
using System;

class Program {
    static void Main() {
        int[] numbers = { 10, 20, 30, 40, 50 };
        
        // Доступ до елементів з початку
        Console.WriteLine($"numbers[0] = {numbers[0]}");   // 10
        Console.WriteLine($"numbers[1] = {numbers[1]}");   // 20
        
        // Доступ до елементів з кінця (новий синтаксис)
        Console.WriteLine($"numbers[^1] = {numbers[^1]}"); // 50 (останній)
        Console.WriteLine($"numbers[^2] = {numbers[^2]}"); // 40 (передостанній)
        Console.WriteLine($"numbers[^3] = {numbers[^3]}"); // 30
        
        // Рядки теж підтримують індекси
        string text = "Hello, C#!";
        Console.WriteLine($"text[^1] = '{text[^1]}'");     // '!'
        Console.WriteLine($"text[^3] = '{text[^3]}'");     // 'C'
        
        // Список
        var list = new List<string> { "A", "B", "C", "D" };
        Console.WriteLine($"list[^1] = {list[^1]}");       // "D"
    }
}

Як працюють індекси?

Вираз array[^n] перетворюється компілятором на array[array.Length - n].

Приклад перетворення індексів
int[] arr = { 1, 2, 3, 4, 5 };

// Ці вирази еквівалентні:
int last1 = arr[^1];           // Синтаксис C# 8.0+
int last2 = arr[arr.Length - 1]; // Традиційний синтаксис

// І так теж:
int secondLast1 = arr[^2];
int secondLast2 = arr[arr.Length - 2];

Console.WriteLine($"arr[^1] = {last1}, arr[arr.Length-1] = {last2}");
Console.WriteLine($"arr[^2] = {secondLast1}, arr[arr.Length-2] = {secondLast2}");

Важливі моменти:

  • ^0 викличе виняток при прямому доступі
  • Індекси працюють з масивами, рядками, Span, списками та іншими колекціями
  • Можна створювати свої типи, що підтримують індекси

Демонстрація індексів

Діапазони в C# 8.0+

Діапазони дозволяють отримувати зрізи (слайси) масивів та інших колекцій за допомогою оператора ...

Синтаксис діапазонів:

  • array[..] - весь масив (копія)
  • array[1..] - з елемента з індексом 1 до кінця
  • array[..^1] - всі елементи крім останнього
  • array[1..4] - елементи з індексами 1, 2, 3
  • array[^3..^1] - останні 3 елементи (крім найостаннішого)
Приклади використання діапазонів
using System;

class Program {
    static void Main() {
        int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        
        // Повний діапазон (копія масиву)
        int[] all = numbers[..];
        Console.WriteLine($"all: {string.Join(", ", all)}");
        
        // Від початку до 4-го елемента (не включаючи)
        int[] first4 = numbers[..4];
        Console.WriteLine($"first4: {string.Join(", ", first4)}");
        
        // Від 2-го елемента до кінця
        int[] from2 = numbers[2..];
        Console.WriteLine($"from2: {string.Join(", ", from2)}");
        
        // З 2-го до 6-го елемента
        int[] slice2to6 = numbers[2..6];
        Console.WriteLine($"slice2to6: {string.Join(", ", slice2to6)}");
        
        // Останні 3 елементи
        int[] last3 = numbers[^3..];
        Console.WriteLine($"last3: {string.Join(", ", last3)}");
        
        // Всі крім першого та останнього
        int[] middle = numbers[1..^1];
        Console.WriteLine($"middle: {string.Join(", ", middle)}");
        
        // З передостаннього до передпередостаннього
        int[] tricky = numbers[^3..^1];
        Console.WriteLine($"tricky: {string.Join(", ", tricky)}");
        
        // З рядками
        string text = "Hello, World!";
        string hello = text[..5];      // "Hello"
        string world = text[7..^1];    // "World"
        
        Console.WriteLine($"hello = '{hello}', world = '{world}'");
    }
}

Тип Range

Оператор .. створює значення типу Range.

Робота з типом Range явно
using System;

class Program {
    static void Main() {
        int[] numbers = { 0, 1, 2, 3, 4, 5 };
        
        // Явне створення діапазонів
        Range firstHalf = 0..3;
        Range lastTwo = ^2..;
        Range middleRange = 1..^1;
        
        // Використання діапазонів
        var slice1 = numbers[firstHalf];    // [0, 1, 2]
        var slice2 = numbers[lastTwo];      // [4, 5]
        var slice3 = numbers[middleRange];  // [1, 2, 3, 4]
        
        // Перевірка меж діапазону
        var range = 2..5;
        Console.WriteLine($"Start: {range.Start}, End: {range.End}");
        Console.WriteLine($"Start value: {range.Start.Value}, " +
                         $"IsFromEnd: {range.Start.IsFromEnd}");
        
        // Діапазон за замовчуванням - весь масив
        Range all = ..;
        Console.WriteLine($"All range: {string.Join(", ", numbers[all])}");
    }
}

Порівняння синтаксису

Новий синтаксис Старий синтаксис Опис
arr[..] arr[0..arr.Length] Весь масив
arr[1..] arr[1..arr.Length] З другого елемента
arr[..^1] arr[0..(arr.Length-1)] Всі крім останнього
arr[^3..] arr[(arr.Length-3)..arr.Length] Останні три

Демонстрація діапазонів

Індексатори в C#

Індексатори дозволяють звертатися до об'єктів як до масивів, використовуючи синтаксис об'єкт[індекс].

Коли використовувати індексатори:

  • Класи, які представляють колекції
  • Об'єкти з внутрішнім масивом або списком
  • Кастомні структури даних
  • Обгортки над словниками або іншими колекціями
Базовий приклад індексатора
using System;

// Клас із простим індексатором
class StringCollection {
    private string[] items = new string[10];
    
    // Індексатор для доступу за цілочисельним індексом
    public string this[int index] {
        get {
            if (index < 0 || index >= items.Length)
                throw new IndexOutOfRangeException();
            return items[index];
        }
        set {
            if (index < 0 || index >= items.Length)
                throw new IndexOutOfRangeException();
            items[index] = value;
        }
    }
    
    public int Count => items.Length;
}

class Program {
    static void Main() {
        var collection = new StringCollection();
        
        // Використовуємо індексатор як масив
        collection[0] = "Hello";
        collection[1] = "World";
        collection[2] = "C#";
        
        Console.WriteLine($"collection[0] = {collection[0]}");
        Console.WriteLine($"collection[1] = {collection[1]}");
        Console.WriteLine($"collection[2] = {collection[2]}");
        
        // Перебір через індексатор
        for (int i = 0; i < 3; i++) {
            Console.WriteLine($"collection[{i}] = {collection[i]}");
        }
    }
}

Багатовимірні індексатори

Матриця з двовимірним індексатором
using System;

class Matrix {
    private double[,] data;
    private int rows, cols;
    
    public Matrix(int rows, int cols) {
        this.rows = rows;
        this.cols = cols;
        data = new double[rows, cols];
    }
    
    // Двовимірний індексатор
    public double this[int row, int col] {
        get {
            ValidateIndex(row, col);
            return data[row, col];
        }
        set {
            ValidateIndex(row, col);
            data[row, col] = value;
        }
    }
    
    private void ValidateIndex(int row, int col) {
        if (row < 0 || row >= rows || col < 0 || col >= cols)
            throw new IndexOutOfRangeException(
                $"Індекс [{row},{col}] поза межами [{rows},{cols}]");
    }
    
    public void Print() {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                Console.Write($"{this[i, j]:F2}\t");
            }
            Console.WriteLine();
        }
    }
}

class Program {
    static void Main() {
        var matrix = new Matrix(3, 3);
        
        // Заповнюємо матрицю через індексатор
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                matrix[i, j] = i * 10 + j + 1;
            }
        }
        
        Console.WriteLine("Матриця 3x3:");
        matrix.Print();
        
        // Доступ до окремих елементів
        Console.WriteLine($"\nmatrix[1, 2] = {matrix[1, 2]}");
        Console.WriteLine($"matrix[2, 0] = {matrix[2, 0]}");
    }
}

Індексатори з користувацькими типами індексів

Підтримка індексів та діапазонів
using System;

class SmartArray<T> {
    private T[] data;
    
    public SmartArray(params T[] items) {
        data = items;
    }
    
    // Звичайний індексатор
    public T this[int index] {
        get => data[index];
        set => data[index] = value;
    }
    
    // Індексатор з підтримкою Index (для ^)
    public T this[Index index] {
        get => data[index];
        set => data[index] = value;
    }
    
    // Індексатор з підтримкою Range (для ..)
    public T[] this[Range range] {
        get {
            var (offset, length) = range.GetOffsetAndLength(data.Length);
            var result = new T[length];
            Array.Copy(data, offset, result, 0, length);
            return result;
        }
    }
    
    public void Print() {
        Console.WriteLine($"[{string.Join(", ", data)}]");
    }
}

class Program {
    static void Main() {
        var arr = new SmartArray<int>(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
        
        Console.Write("Вихідний масив: ");
        arr.Print();
        
        // Використовуємо новий синтаксис індексів
        Console.WriteLine($"arr[^1] = {arr[^1]}");      // Останній
        Console.WriteLine($"arr[^3] = {arr[^3]}");      // Передпередостанній
        
        // Використовуємо діапазони
        Console.Write("\narr[2..5] = ");
        var slice1 = arr[2..5];
        Console.WriteLine($"[{string.Join(", ", slice1)}]");
        
        Console.Write("arr[^4..^1] = ");
        var slice2 = arr[^4..^1];
        Console.WriteLine($"[{string.Join(", ", slice2)}]");
        
        Console.Write("arr[..^2] = ");
        var slice3 = arr[..^2];
        Console.WriteLine($"[{string.Join(", ", slice3)}]");
    }
}

Кращі практики:

  • Завжди перевіряйте межі індексів
  • Документуйте поведінку індексаторів
  • Розгляньте підтримку Index та Range у своїх колекціях
  • Не зловживайте індексаторами - використовуйте їх там, де це логічно

Створення кастомного індексатора

Практичні завдання

Розв'яжіть ці задачі для закріплення матеріалу. Натисніть на задачу, щоб побачити рішення.

Завдання 1: Зворотний доступ до масиву

Створіть метод, який повертає масив у зворотному порядку, використовуючи індекси з кінця.

public static T[] ReverseArray<T>(T[] array) {
    T[] result = new T[array.Length];
    for (int i = 0; i < array.Length; i++) {
        result[i] = array[^(i + 1)];  // Використовуємо індекс з кінця
    }
    return result;
}

// Використання:
int[] numbers = { 1, 2, 3, 4, 5 };
var reversed = ReverseArray(numbers);
// reversed = [5, 4, 3, 2, 1]

Завдання 2: Видалити перший та останній елемент

Використовуючи діапазони, створіть метод, який повертає масив без першого та останнього елемента.

public static T[] TrimArray<T>(T[] array) {
    if (array.Length <= 2) 
        return Array.Empty<T>();
    
    return array[1..^1];  // Всі крім першого та останнього
}

// Використання:
string[] words = { "first", "second", "third", "fourth", "last" };
var trimmed = TrimArray(words);
// trimmed = ["second", "third", "fourth"]

Завдання 3: Клас CircularBuffer з індексатором

Створіть клас кільцевого буфера з індексатором, який підтримує доступ за індексом з кінця.

public class CircularBuffer<T> {
    private T[] buffer;
    private int start;
    
    public CircularBuffer(int capacity) {
        buffer = new T[capacity];
        start = 0;
    }
    
    public int Count { get; private set; }
    
    // Індексатор з підтримкою Index
    public T this[Index index] {
        get {
            int i = index.GetOffset(Count);
            if (i < 0 || i >= Count)
                throw new IndexOutOfRangeException();
            return buffer[(start + i) % buffer.Length];
        }
        set {
            int i = index.GetOffset(Count);
            if (i < 0 || i >= Count)
                throw new IndexOutOfRangeException();
            buffer[(start + i) % buffer.Length] = value;
        }
    }
    
    public void Add(T item) {
        buffer[(start + Count) % buffer.Length] = item;
        if (Count < buffer.Length) {
            Count++;
        } else {
            start = (start + 1) % buffer.Length;
        }
    }
}

// Використання:
var buffer = new CircularBuffer<int>(5);
for (int i = 1; i <= 7; i++) buffer.Add(i);
// buffer містить [3, 4, 5, 6, 7]
Console.WriteLine($"Останній елемент: {buffer[^1]}");  // 7
Console.WriteLine($"Перший елемент: {buffer[0]}");     // 3

Завдання 4: Розбиття рядка на частини

Використовуючи діапазони, розбийте рядок на рівні частини заданого розміру.

public static string[] SplitIntoChunks(string text, int chunkSize) {
    if (chunkSize <= 0) 
        throw new ArgumentException("Розмір частини має бути додатнім");
    
    int chunks = (int)Math.Ceiling(text.Length / (double)chunkSize);
    string[] result = new string[chunks];
    
    for (int i = 0; i < chunks; i++) {
        int start = i * chunkSize;
        int end = Math.Min(start + chunkSize, text.Length);
        result[i] = text[start..end];  // Використовуємо діапазон
    }
    
    return result;
}

// Використання:
string text = "HelloWorldCSharp";
var chunks = SplitIntoChunks(text, 5);
// chunks = ["Hello", "World", "CShar", "p"]

Завдання 5: Кастомна колекція з діапазонами

Створіть колекцію, яка підтримує індекси, діапазони та має індексатор.

public class CustomCollection<T> {
    private List<T> items = new List<T>();
    
    public void Add(T item) => items.Add(item);
    public int Count => items.Count;
    
    // Звичайний індексатор
    public T this[int index] {
        get => items[index];
        set => items[index] = value;
    }
    
    // Індексатор з Index
    public T this[Index index] {
        get => items[index];
        set => items[index] = value;
    }
    
    // Індексатор з Range
    public List<T> this[Range range] {
        get {
            var (offset, length) = range.GetOffsetAndLength(items.Count);
            return items.GetRange(offset, length);
        }
    }
    
    public override string ToString() {
        return $"[{string.Join(", ", items)}]";
    }
}

// Використання:
var collection = new CustomCollection<int>();
for (int i = 0; i < 10; i++) collection.Add(i * 10);

Console.WriteLine($"collection[^1] = {collection[^1]}");  // 90
Console.WriteLine($"collection[1..4] = [{string.Join(", ", collection[1..4])}]");

Пісочниця для експериментів

Напишіть свій код і запустіть його прямо в браузері!

Редактор коду:

Результат виконання:

Тут буде результат виконання вашого коду...

Готові приклади:

Назад Вперед Зміст