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].
Памʼятайте: індексатори мають логічно відповідати природі вашого класу. Якщо клас не є колекцією або структурою даних, швидше за все, індексатор йому не потрібен. Але якщо ваш клас зберігає й керує набором елементів, індексатор може зробити його використання набагато зручнішим і природнішим.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ