JavaRush /Курсы /C# SELF /Знакомство с индексаторами

Знакомство с индексаторами

C# SELF
18 уровень , 0 лекция
Открыта

1. Пару слов о коллекциях

До сих пор мы работали с массивами — это простейший способ хранить несколько элементов одного типа. Но в C# есть множество более удобных и мощных коллекций, которые решают различные задачи.

Коллекция — это объект, который может хранить группу других объектов. В отличие от массивов, коллекции часто могут изменять свой размер динамически, предоставляют удобные методы для работы с данными и оптимизированы для разных сценариев использования.

Основные типы коллекций (краткий обзор):

List<T> — динамический массив, который может расти и сжиматься:

List<string> names = new List<string>();
names.Add("Алекс");
names.Add("Мария");
Console.WriteLine(names[0]); // Алекс
Console.WriteLine(names.Count); // 2

Dictionary<TKey, TValue> — коллекция пар "ключ-значение", где каждому ключу соответствует одно значение:

Dictionary<string, int> ages = new Dictionary<string, int>();
ages["Алекс"] = 25;
ages["Мария"] = 30;
Console.WriteLine(ages["Алекс"]); // 25

Обратите внимание: в примерах выше мы используем квадратные скобки [] для доступа к элементам коллекций — точно так же, как с массивами! Это возможно благодаря индексаторам, о которых мы сегодня и поговорим.

Это только первое знакомство с коллекциями — подробно их возможности, отличия и применение мы изучим в следующих лекциях. Сейчас важно понять, что коллекции позволяют обращаться к своим элементам через квадратные скобки, и это не магия, а специальный механизм языка.

2. Индексаторы

Обычные объекты C# работают через свойства и методы. Но что делать, если ваш объект — это некая мини-коллекция? Например, представьте:

  • Вы пишете класс Week, который должен возвращать название дня недели по номеру: week[0] → "Понедельник".
  • Или класс Library, где можно обращаться к книге по её номеру: library[3] → "В сторону Свана".

Звучит удобно, верно? Было бы странно писать library.GetBookByIndex(3) каждый раз — хочется обращаться к своему объекту, как к массиву!

Вот тут-то и появляются индексаторы.

Индексатор — это особый член класса, который позволяет использовать объекты этого класса с синтаксисом квадратных скобок, как массивы: obj[0], obj["ключ"], и так далее.

Такой индексатор выглядит внешне как свойство, но вместо имени — принимает параметры внутри квадратных скобок. Это похоже на то, как мы пишем .Name, только вместо этого — [i].

3. Простая коллекция с индексатором

Давайте соберем мини-класс, который будет хранить любимые цвета. Используем для хранения массив строк. Без индексатора пришлось бы делать метод вроде GetColor(int i). Но с индексатором:


using System;

public class FavoriteColors
{
   // Приватное поле для хранения цветов
   private string[] colors = new string[5];

   // Индексатор:
   public string this[int index]
   {
       get
       {
           // Контроль границ массива (инкапсуляция в действии!)
           if (index < 0 || index >= colors.Length)
               throw new IndexOutOfRangeException("Неверный индекс цвета!");

           return colors[index];
       }
       set
       {
           if (index < 0 || index >= colors.Length)
               throw new IndexOutOfRangeException("Неверный индекс цвета!");

           colors[index] = value ?? throw new ArgumentNullException(nameof(value));
       }
   }
}

class Program
{
   static void Main()
   {
       FavoriteColors favorites = new FavoriteColors();

       favorites[0] = "Зелёный";
       favorites[1] = "Синий";
       favorites[2] = "Красный";
       favorites[10] = "Фиолетовый"; // Бросает исключение!

       Console.WriteLine(favorites[1]); // Синий
   }
}

Что здесь происходит?

  • Мы создаём приватный массив, чтобы никто снаружи не мог с ним баловаться напрямую.
  • Индексатор определён как public string this[int index], где this — ключевое слово, указывающее, что индексатор относится к объекту.
  • Внутри get и set мы контролируем диапазон, не даём выйти за границы/записать null.
  • На выходе мы можем делать favorites[0], как с обычным массивом.

4. Синтаксис индексатора: подробности

Синтаксис похож на свойство, но вместо имени (например, Age) указывается ключевое слово this с параметрами:


// Сигнатура индексатора (общий шаблон)
[модификатор] ТипРезультата this[ТипИндекса имяИндекса]
{
   get { ... }
   set { ... }
}

Пример: Классический

public class MyCollection
{
   private int[] data = new int[10];

   // Индексатор для чтения и записи
   public int this[int index]
   {
       get { return data[index]; }
       set { data[index] = value; }
   }
}

Индексаторы бывают не только по int

Основная фишка: индексатор не обязан принимать только int. Вы можете назначить любой тип (главное, чтобы обращение по ключу имело смысл):

public string this[string colorName]
{
   get { /* ... */ }
   set { /* ... */ }
}

Например, в классе телефонной книги логично искать по имени:

public class PhoneBook
{
   private Dictionary<string, int> entries = new Dictionary<string, int>();

   public int this[string name]
   {
       get
       {
           if (entries.ContainsKey(name))
               return entries[name];
           return null;
       }
       set
       {
           entries[name] = value;
       }
   }
}

О коллекциях и как работает Dictionary<string, string> я расскажу в будущих лекциях :P

5. Практический пример: Счетчик слов в тексте

Продолжим развивать наше приложение. Допустим, теперь у нас есть класс, который считает, сколько раз каждое слово встретилось в тексте. Удобно, если пользователь сможет обращаться к объекту через квадратные скобки, чтобы получить количество по слову:


using System.Collections.Generic;

public class WordCounter
{
   private Dictionary<string, int> counter = new Dictionary<string, int>();

   // Индексатор по строке (слово)
   public int this[string word]
   {
       get
       {
           if (counter.ContainsKey(word))
               return counter[word];
           return 0; // Если такого слова нет, возвращаем 0.
       }
       set
       {
           counter[word] = value;
       }
   }

   // Метод для подсчета слов из строки
   public void AddWords(string text)
   {
       foreach (var word in text.Split(' ', System.StringSplitOptions.RemoveEmptyEntries))
       {
           if (counter.ContainsKey(word))
               counter[word]++;
           else
               counter[word] = 1;
       }
   }
}

// В Main:
var wc = new WordCounter();
wc.AddWords("мама мыла раму мыла мама папа");
Console.WriteLine($"'мама' встречается {wc["мама"]} раз(а)");
Console.WriteLine($"'рама' встречается {wc["рама"]} раз(а)");
Console.WriteLine($"'кот' встречается {wc["кот"]} раз(а)"); // 0

Для чего это нужно на практике? Такой подход часто используется для реализации собственных коллекций, библиотек памяти, маппингов (словари и индексы) и даже DSL (специализированных языков внутри C#).

6. Ограничения и нюансы

Индексаторы — вещь мощная, но есть несколько правил и подводных камней (куда же без них).

Названия у индексатора нет

В отличие от свойства, у индексатора нет имени, только сигнатура вида this[тип параметра]. Если вы начнёте писать public int MyIndexer[int i] — компилятор удивится. Используем только this.

Не бывает статических индексаторов

Индексаторы всегда создаются для экземпляра класса, а не для статических членов. То есть нельзя объявить static int this[int i] — логика в том, что this всегда указывает на конкретный экземпляр объекта.

Могут быть перегружены по типу/числу параметров

Вы можете создать несколько индексаторов в одном классе, если их параметры различаются по типу или количеству. Например:

public string this[int i] { get { ... } set { ... } }
public string this[string key] { get { ... } set { ... } }

Это легально, компилятор не запутается — и напомнит вам, если параметры пересекаются.

Поддержка только через свойства

Нельзя объявить индексатор без аксессоров get или set. Если вы хотите только для чтения — уберите set, только для записи — get. Обычно используют оба.

7. Практическая польза и зачем это знать

  • Индексаторы активно применяются в коллекциях данных. Многие классы .NET имеют их: например, List<T>, Dictionary<TKey,TValue>. Когда вы пишете list[2], вы используете именно индексатор!
  • Индексаторы позволяют скрыть внутреннюю реализацию (инкапсуляция!), но предоставить удобный и привычный интерфейс. Пользователь вашего класса не задумывается, как вы храните данные, он просто использует привычные [index].
  • Ваш код становится лаконичным и интуитивно понятным — что особенно ценят на собеседованиях (и ваши будущие коллеги).

Свойства и Индексаторы: сравнение

Свойство Индексатор
Имя Да (например, Name) Нет (вместо имени — this[параметр])
Доступ По имени По индексу (или другому ключу)
Статика Может быть static Только для экземпляра
Много в классе Да, любое количество Да, но с разной сигнатурой параметров
Применение Хранение/доступ к данным Мини-коллекции, ассоциативные данные

8. Типичные ошибки и советы

Ошибка №1: попытка сделать индексатор static.
Так нельзя — this[...] работает только с объектом.

Ошибка №2: забыли проверку индекса.
Если не проверить границы массива в get или set, программа может завершиться аварийно.

Ошибка №3: перепутаны типы параметров.
Если создать два индексатора с одинаковыми параметрами, компилятор выдаст ошибку.

Ошибка №4: забыли реализовать get или set.
Если нужен доступ и на чтение, и на запись — оба должны быть реализованы.

Совет: если ваш класс оборачивает массив — просто передавайте обращения к массиву через индексатор. Это ускорит работу и сделает код интуитивным.

9. Зачем всё это знать

Индексаторы делают интерфейс объекта простым, понятным и удобным. Они позволяют скрыть внутреннюю реализацию, но оставить разработчику удобный способ доступа к данным.

Именно так реализованы встроенные коллекции: string, List<T>, Dictionary<TKey, TValue>, Span<T> и многие другие. Когда вы пишете array[2] или text[0], вы уже используете индексатор.

А самое главное — вы теперь можете писать свои классы, которые работают так же гибко и лаконично. А значит — вы шаг ближе к написанию профессионального и читаемого кода.

2
Задача
C# SELF, 18 уровень, 0 лекция
Недоступна
Создание класса "Week" с индексатором по дням недели
Создание класса "Week" с индексатором по дням недели
2
Задача
C# SELF, 18 уровень, 0 лекция
Недоступна
Телефонный справочник с индексатором по имени
Телефонный справочник с индексатором по имени
Комментарии (4)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Yurii N Уровень 30
7 января 2026

get
       {
           if (entries.ContainsKey(name))
               return entries[name];
           return null;
       }
такое не скомпилируется, нужно такую сигнатуру ставить: public int? this[string name]
Александр Уровень 22
3 декабря 2025
очепяток куча в тексте. пропущены союзы и т.д.
Ilya Уровень 31
1 декабря 2025
Согласно тексту первого задания - у четверга будет индекс не 4 а 3. Благо с "пятницей" тоже валидация прокатывает... А вот второе задание - огонь!👏
Ra Уровень 35 Student
24 ноября 2025
Сомнительно но окей.