JavaRush /Курси /C# SELF /Розширені індексатори в C#: параметри, модифікатори, суча...

Розширені індексатори в C#: параметри, модифікатори, сучасні можливості

C# SELF
Рівень 18 , Лекція 1
Відкрита

1. Індексатори з кількома параметрами

Простий індексатор (наприклад, this[int index]) зручний, коли вам потрібно звертатися до елементів за цілим індексом — майже як у масиві. Але C# дозволяє набагато більше: можна використовувати кілька параметрів, параметри з різними типами, встановлювати різні модифікатори доступу для get і set, реалізовувати кілька індексаторів в одному класі (якщо їхні сигнатури різні).

Також у нових версіях C# зʼявилися додаткові можливості, що спрощують життя під час роботи з індексаторами, включно із синтаксичним цукром для лаконічного коду.

Індексатор узагалі не зобовʼязаний приймати лише один індекс і лише типу int. Його параметри визначаєте ви — можна мати кілька параметрів різних типів, якщо цього потребує завдання.


public class ChessBoard
{
    private string[,] board = new string[8, 8];

    // Індексатор із двома параметрами!
    public string this[int row, int col]
    {
        get { return board[row, col]; }
        set { board[row, col] = value; }
    }
}
Індексатор із двома параметрами для двовимірної структури

Тепер можемо писати код так:

ChessBoard chess = new ChessBoard();
chess[0, 0] = "Тура";
chess[7, 7] = "Король";
string piece = chess[0, 0]; // "Тура"

Такий підхід добре підходить для матриць, двовимірних карт, настільних ігор, складних колекцій.

2. Індексатори з параметрами різних типів

Ваш індексатор може приймати параметри не лише типу int, а й будь-якого іншого, що підходить. Головне — щоб це було логічно для вашого завдання.


using System.Collections.Generic;

public class Employee
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Position { get; set; }
}

public class EmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    // Індексатор за імʼям співробітника (string)
    public Employee this[string name]
    {
        get
        {
            foreach (var employee in employees)
            {
                if (employee.Name == name)
                    return employee;
            }
            return null; // Або можна викинути виняток
        }
        set
        {
            for (int i = 0; i < employees.Count; i++)
            {
                if (employees[i].Name == name)
                {
                    employees[i] = value;
                    return;
                }
            }
            // Якщо співробітника з таким імʼям немає — додаємо нового
            employees.Add(value);
        }
    }

    // Для сумісності — індексатор за числовим індексом
    public Employee this[int index]
    {
        get { return employees[index]; }
        set { employees[index] = value; }
    }
}
var company = new EmployeeCollection();
company[0] = new Employee { Name = "Іван", Age = 30, Position = "Програміст" };
company[1] = new Employee { Name = "Марія", Age = 25, Position = "Дизайнер" };

Employee employee = company["Іван"];
company["Петро"] = new Employee { Name = "Петро", Age = 28, Position = "Тестувальник" };

Важливо: якщо у вас кілька індексаторів — їхні сигнатури мають відрізнятися за набором і типами параметрів.

3. Різні модифікатори доступу для get і set

Іноді потрібно дозволити тільки читання за індексом, а запис заборонити (або навпаки). У C# можна встановлювати різні модифікатори доступу для get- і set-аксесорів індексатора.


public class SecureEmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    public Employee this[int index]
    {
        get { return employees[index]; }
        internal set { employees[index] = value; }
    }
}

Це часто використовують для захисту колекцій від несанкціонованих змін, роблячи клас більш керованим.

4. Read-only та Write-only індексатори

Іноді потрібно дозволити тільки читання або тільки запис через індексатор.


public class ReadOnlyEmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    public Employee this[int index]
    {
        get { return employees[index]; }
        // set відсутній — не можна змінювати!
    }
}

public class WriteOnlyEmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    public Employee this[int index]
    {
        set
        {
            employees.Insert(index, value);
        }
        // get відсутній — не можна читати!
    }
}

У реальних проєктах «write-only» майже не трапляється: зазвичай потрібні «read-only», наприклад, коли з класу назовні можна лише переглядати, але не змінювати дані через індексатор.

5. Індексатори з перевіркою меж і логікою

У якісних реалізаціях важливо не лише надати доступ за індексом, а й коректно обробляти вихід за межі, помилки пошуку та інші виняткові ситуації.


public class SafeEmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    public Employee this[int index]
    {
        get
        {
            if (index < 0 || index >= employees.Count)
                throw new IndexOutOfRangeException("Співробітника з таким індексом не існує!");
            return employees[index];
        }
        set
        {
            if (index < 0 || index >= employees.Count)
                throw new IndexOutOfRangeException("Не можна замінити неіснуючого співробітника!");
            employees[index] = value;
        }
    }
}

Можна повертати null або використовувати сучасні підходи до обробки відсутніх значень — вибір залежить від логіки вашого застосунку.

6. Індексатори з незвичними параметрами

Ви можете побачити колекції, де індексатор приймає нестандартні типи: перерахування (enum), користувацькі структури, навіть кілька параметрів різних типів.


public enum Department { IT, HR, Finance, Marketing }

public class DepartmentEmployeeCollection
{
    private Dictionary<Department , Employee> departmentLeads = new Dictionary<Department , Employee>();

    public Employee this[Department  department]
    {
        get { return departmentLeads.TryGetValue(department, out var employee) ? employee : null; }
        set { departmentLeads[department] = value; }
    }
}
var company = new DepartmentEmployeeCollection();
company[Department.IT] = new Employee { Name = "Анна", Age = 35, Position = "Керівник IT" };
Employee itLead = company[Department.IT];

Такий підхід доречний, коли у вас є унікальні ідентифікатори або чітка відповідність типу й значення — зручно, зрозуміло й типобезпечно.

7. Сучасні можливості: Range та Index

З появою типів Range і Index у C# 8 індексатори отримали нові можливості для роботи з діапазонами і індексами від кінця:


public class SmartArray
{
    private int[] numbers = Enumerable.Range(0, 100).ToArray();

    public int[] this[Range range] => numbers[range];
    public int this[Index index] => numbers[index];
}

// Використання:
var smart = new SmartArray();
int[] middle = smart[20..30]; // з 20-го по 29-й елементи
int last = smart[^1]; // останній елемент

Якщо ви реалізуєте власну колекцію, підтримка Range і Index робить її максимально природною та зручною для розробників.

8. Властивості проти індексаторів

Щоб краще зрозуміти, коли використовувати властивості, а коли індексатори, корисно порівняти їхні особливості.

  • Властивості зручні для доступу до індивідуальних характеристик обʼєкта за імʼям: person.Name, car.Speed.
  • Індексатори підходять для доступу до елементів колекції або структури за ключем: employees[0], phoneBook["Іван"].
  • Властивість завжди має конкретне імʼя, і в класі може бути лише одна властивість з цим імʼям.
  • Індексатор використовує ключове слово this і може мати кілька варіантів, якщо їхні сигнатури різні.
  • Властивості можуть бути статичними, індексатори — ні, бо this завжди вказує на конкретний екземпляр обʼєкта.

9. Типові помилки під час роботи з індексаторами

Помилка №1: відсутність перевірки меж.
Якщо не перевірити індекс на вихід за межі масиву, можна отримати IndexOutOfRangeException у найнесподіваніший момент.

Помилка №2: ігнорування можливого null, що повертає індексатор.
Якщо індексатор за рядком повертає null за відсутності елемента, а код, що викликає, бездумно звертається до результату, виникає NullReferenceException.

Помилка №3: дублювання сигнатур індексаторів.
C# не дозволяє створювати два індексатори з однаковими наборами параметрів.

Помилка №4: неочевидна логіка в set-аксесорі.
Якщо в set використовується логіка «додавання за відсутності», а не заміни, це може бентежити. Такі рішення варто робити явними й добре документувати.

10. Практичне застосування і висновок

У реальних проєктах індексатори часто використовують для створення спеціалізованих колекцій, кешів, словників із додатковою логікою, матриць і багатовимірних структур даних. Вони роблять код читабельнішим і інтуїтивно зрозумілим — замість виклику методів на кшталт GetElementByIndex(5) можна просто написати collection[5].

Памʼятайте: індексатори мають логічно відповідати природі вашого класу. Якщо клас не є колекцією або структурою даних, швидше за все, індексатор йому не потрібен. Але якщо ваш клас зберігає й керує набором елементів, індексатор може зробити його використання набагато зручнішим і природнішим.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ