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].

Помните: индексаторы должны логически соответствовать природе вашего класса. Если класс не является коллекцией или структурой данных, вероятно, индексатор ему не нужен. Но если ваш класс хранит и управляет набором элементов, индексатор может сделать его использование гораздо более удобным и естественным.

2
Задача
C# SELF, 18 уровень, 1 лекция
Недоступна
Реализация индексатора для двухмерного массива
Реализация индексатора для двухмерного массива
2
Задача
C# SELF, 18 уровень, 1 лекция
Недоступна
Индексатор с перечислением (enum)
Индексатор с перечислением (enum)
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ