Индексаторы (Руководство по программированию в C#)
Индексаторы позволяют индексировать экземпляры класса или структуры точно так же, как и массивы. Индексированное значение можно задавать или получать без явного указания типа или экземпляра элемента. Индексаторы действуют как свойства, за исключением того, что их акцессоры принимают параметры.
В следующем примере определяется универсальный класс с простыми акцессорами get и set для назначения и получения значений. Класс Program создает экземпляр этого класса для хранения строк.
using System; class SampleCollection < // Declare an array to store the data elements. private T[] arr = new T[100]; // Define the indexer to allow client code to use [] notation. public T this[int i] < get < return arr[i]; >set < arr[i] = value; >> > class Program < static void Main() < var stringCollection = new SampleCollection(); stringCollection[0] = "Hello, World"; Console.WriteLine(stringCollection[0]); > > // The example displays the following output: // Hello, World.
Дополнительные примеры см. в разделе Связанные разделы.
Определения текста выражений
Довольно часто акцессор get или set индексатора состоит из одной инструкции, которая просто возвращает или задает значение. Члены, воплощающие выражение, предоставляют упрощенный синтаксис для поддержки такого варианта использования. Начиная с версии C# 6, доступные только для чтения индексаторы можно реализовать в виде члена, воплощающего выражение, как показано в следующем примере.
using System; class SampleCollection < // Declare an array to store the data elements. private T[] arr = new T[100]; int nextIndex = 0; // Define the indexer to allow client code to use [] notation. public T this[int i] =>arr[i]; public void Add(T value) < if (nextIndex >= arr.Length) throw new IndexOutOfRangeException($"The collection can hold only elements."); arr[nextIndex++] = value; > > class Program < static void Main() < var stringCollection = new SampleCollection(); stringCollection.Add("Hello, World"); System.Console.WriteLine(stringCollection[0]); > > // The example displays the following output: // Hello, World.
Обратите внимание, что => представляет тело выражения, а ключевое слово get не используется.
Начиная с версии C# 7.0, методы доступа get и set можно реализовывать в виде членов с телом в виде выражения. В этом случае необходимо указывать оба ключевых слова ( get и set ). Пример:
using System; class SampleCollection < // Declare an array to store the data elements. private T[] arr = new T[100]; // Define the indexer to allow client code to use [] notation. public T this[int i] < get =>arr[i]; set => arr[i] = value; > > class Program < static void Main() < var stringCollection = new SampleCollection(); stringCollection[0] = "Hello, World."; Console.WriteLine(stringCollection[0]); > > // The example displays the following output: // Hello, World.
Общие сведения об индексаторах
- Индексаторы позволяют индексировать объекты так же, как и массивы.
- Метод доступа get возвращает значение. Метод доступа set назначает значение.
- Ключевое слово this используется для определения индексаторов.
- Ключевое слово value используется для определения значения, присваиваемого методом доступа .
- Индексаторы не нужно индексировать по целому значению; пользователь может определить конкретный механизм поиска на свое усмотрение.
- Индексаторы могут быть перегружены.
- Индексаторы могут иметь более одного формального параметра, например при доступе к двумерному массиву.
Связанные разделы
- Использование индексаторов
- Индексаторы в интерфейсах
- Сравнение свойств и индексаторов
- Ограничение доступности методов доступа
Спецификация языка C#
Дополнительные сведения см. в разделе Индексаторы в Спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
- Руководство по программированию на C#
- Свойства
Использование индексаторов. Руководство по программированию на C#
Применение индексаторов упрощает работу с синтаксисом, позволяя создавать классы, структуры и интерфейсы, к которым клиентские приложения могут обращаться так же, как к массиву. В этом случае компилятор создает свойство Item (или свойство с другим именем, если присутствует IndexerNameAttribute) и соответствующие методы доступа. Индексаторы чаще всего реализуются в типах, предназначенных преимущественно для инкапсуляции внутренней коллекции или массива. Допустим, у вас есть класс TempRecord , представляющий журнал с 10 измерениями температуры по шкале Фаренгейта за период в 24 часа. Этот класс содержит массив temps типа float[] для хранения значений температуры. Реализация индексатора в этом классе позволит клиентам получать доступ к значениям температуры в экземпляре TempRecord , используя float temp = tempRecord[4] вместо float temp = tempRecord.temps[4] . Это позволяет не только упростить синтаксис клиентских приложений, но и облегчить понимание кода класса и его предназначения другими разработчиками.
Чтобы объявить индексатор для класса или структуры, используйте ключевое слово this, как в следующем примере:
// Indexer declaration public int this[int index] < // get and set accessors >
При объявлении индексатора для объекта автоматически создается свойство с именем Item . Свойство Item не будет доступно непосредственно из выражения доступа к члену экземпляра. Кроме того, если вы добавите к объекту с индексатором собственное свойство Item , возникнет ошибка компилятора CS0102. Чтобы избежать этого, используйте IndexerNameAttribute, чтобы переименовать индексатор, как описывается ниже.
Замечания
Тип индексатора и типы его параметров должны иметь по крайней мере такой же уровень доступности, как и сам индексатор. Дополнительные сведения об уровнях доступа см. в разделе Модификаторы доступа.
Дополнительные сведения об использовании индексаторов с интерфейсом см. в разделе Индексаторы интерфейса.
Сигнатура индексатора определяет число и типы его формальных параметров. В ней не указываются тип индексатора или имена его формальных параметров. Если для одного класса объявляется несколько индексаторов, они должны иметь разные сигнатуры.
Индексатор не классифицируется как переменная; Таким образом, значение индексатора невозможно передать по ссылке (как параметру ref out ), если его значение не является ссылкой (т. е. возвращается по ссылке.)
Чтобы присвоить индексатору имя, которое можно использовать в других языках, используйте System.Runtime.CompilerServices.IndexerNameAttribute, как показано в этом примере:
// Indexer declaration [System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] < // get and set accessors >
Этот индексатор будет иметь имя TheItem , поскольку оно переопределено атрибутом имени индексатора. По умолчанию используется имя индексатора Item .
Пример 1
В следующем примере показано, как объявить частное поле массива temps и индексатор. Индексатор обеспечивает прямой доступ к экземпляру tempRecord[i] . Вместо использования индексатора можно объявить массив как элемент public и осуществлять доступ к его элементам напрямую ( tempRecord.temps[i] ).
public class TempRecord < // Array of temperature values float[] temps = [ 56.2F, 56.7F, 56.5F, 56.9F, 58.8F, 61.3F, 65.9F, 62.1F, 59.2F, 57.5F ]; // To enable client code to validate input // when accessing your indexer. public int Length =>temps.Length; // Indexer declaration. // If index is out of range, the temps array will throw the exception. public float this[int index] < get =>temps[index]; set => temps[index] = value; > >
Обратите внимание, что при определении прав доступа индексатора, например в инструкции Console.Write , вызывается метод доступа get. Таким образом, если метод доступа get отсутствует, возникает ошибка времени компиляции.
var tempRecord = new TempRecord(); // Use the indexer's set accessor tempRecord[3] = 58.3F; tempRecord[5] = 60.1F; // Use the indexer's get accessor for (int i = 0; i < 10; i++) < Console.WriteLine($"Element #= "); >
Индексирование с использованием других значений
В C# тип параметра индексатора не ограничивается целочисленными значениями. Например, в качестве индексатора могут использоваться строки. Такой индексатор можно реализовать путем поиска строки в коллекции с возвратом соответствующего значения. Поскольку методы доступа можно перегружать, строковые и целочисленные версии могут сосуществовать.
Пример 2
Этот пример объявляет класс, который хранит названия дней недели. Метод доступа get принимает название дня в виде строкового значения и возвращает соответствующее целое число. Например, для Sunday возвращается значение 0, для Monday — 1 и т. д.
// Using a string as an indexer value class DayCollection < string[] days = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"]; // Indexer with only a get accessor with the expression-bodied definition: public int this[string day] =>FindDayIndex(day); private int FindDayIndex(string day) < for (int j = 0; j < days.Length; j++) < if (days[j] == day) < return j; >> throw new ArgumentOutOfRangeException( nameof(day), $"Day is not supported.\nDay input must be in the form \"Sun\", \"Mon\", etc"); > >
Пример использования 2
var week = new DayCollection(); Console.WriteLine(week["Fri"]); try < Console.WriteLine(week["Made-up day"]); >catch (ArgumentOutOfRangeException e) < Console.WriteLine($"Not supported input: "); >
Пример 3
В этом примере объявляется класс, в котором хранятся названия дней недели с использованием перечисления System.DayOfWeek. Метод доступа get принимает название дня ( DayOfWeek ) в виде строкового значения и возвращает соответствующее целое число. Например, для DayOfWeek.Sunday возвращается 0, для DayOfWeek.Monday — 1 и т. д.
using Day = System.DayOfWeek; class DayOfWeekCollection < Day[] days = [ Day.Sunday, Day.Monday, Day.Tuesday, Day.Wednesday, Day.Thursday, Day.Friday, Day.Saturday ]; // Indexer with only a get accessor with the expression-bodied definition: public int this[Day day] =>FindDayIndex(day); private int FindDayIndex(Day day) < for (int j = 0; j < days.Length; j++) < if (days[j] == day) < return j; >> throw new ArgumentOutOfRangeException( nameof(day), $"Day is not supported.\nDay input must be a defined System.DayOfWeek value."); > >
Пример использования 3
var week = new DayOfWeekCollection(); Console.WriteLine(week[DayOfWeek.Friday]); try < Console.WriteLine(week[(DayOfWeek)43]); >catch (ArgumentOutOfRangeException e) < Console.WriteLine($"Not supported input: "); >
Отказоустойчивость
Повысить безопасность и надежность индексаторов можно двумя способами:
- Реализуйте стратегию обработки ошибок, предусматривающую действия в ситуациях, когда из клиентского кода передается недопустимое значение индекса. В первом примере из этого раздела класс TempRecord содержит свойство Length, с помощью которого клиентский код проверяет введенное значение, прежде чем передать его в индексатор. Кроме того, код обработки ошибок можно поместить в сам индексатор. Не забудьте задокументировать исключения, которые будут вызываться в методе доступа индексатора, для других пользователей.
- Настройте максимально ограничивающие уровни доступа для методов доступа get и set. Особенно важно сделать это для метода доступа set . Дополнительные сведения см. в разделе Доступность методов доступа.
См. также
Совместная работа с нами на GitHub
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
Есть ли смысл применять индексаторы?
Появилась необходимость разобраться с индексаторами. Тема не сложная но лучше проработать на будущее. Зачем они нужны? Я так понял для того чтобы при создании экземпляра класса мы могли сразу работать с несколькими экземплярами нашего класса, так что-ли? Почему бы нам просто не сделать коллекцию например для работы с несколькими экземплярами класса? Интересуют насколько полезно употреблять индексаторы? Они несут практическую пользу типо уменьшение затрат ресурсов на работу программы, или например ускорение работы программы?
Отслеживать
задан 18 янв 2019 в 17:02
Gnom Skull Gnom Skull
513 4 4 серебряных знака 19 19 бронзовых знаков
Цитата «Индексаторы позволяют индексировать объекты и обращаться к данным по индексу. Фактически с помощью индексаторов мы можем работать с объектами как с массивами.» Для этого и нужны.
18 янв 2019 в 17:22
Индексатор это то же свойство, только параметризированное, т.е семантически связанная пара методов (ну или один метод), но дополнительно несущее семантику Item. Вот вы и должны думать, должен ваш класс нести такой смысл или нет, это решение полностью на вашей совести. Любой код, использующий индексатор можно переписать без него, с обычными методами. Никаких ускорений или замедлений индексаторы не дают.
18 янв 2019 в 17:39
Что интересно, в CIL есть поддержка и других параметризированных свойств, помимо индексатора, чего нет в C#, но есть, например, в VB
18 янв 2019 в 17:42
Ну и как и любое свойство, индексатор может возвращать любой тип, например, если вы напишете класс шахматная доска, то он может возвращать с помощью индексатора конкретную ячейку: Cell this[int x, int y] < . >
Зачем нужны индексаторы c
Индексаторы позволяют индексировать объекты и обращаться к данным по индексу. Фактически с помощью индексаторов мы можем работать с объектами как с массивами. По форме они напоминают свойства со стандартными блоками get и set , которые возвращают и присваивают значение.
Формальное определение индексатора:
возвращаемый_тип this [Тип параметр1, . ] < get < . >set < . >>
В отличие от свойств индексатор не имеет названия. Вместо него указывается ключевое слово this , после которого в квадратных скобках идут параметры. Индексатор должен иметь как минимум один параметр.
Посмотрим на примере. Допустим, у нас есть класс Person , который представляет человека, и класс Company , который представляет некоторую компанию, где работают люди. Используем индексаторы для определения класса Company:
class Person < public string Name < get;>public Person(string name) => Name=name; > class Company < Person[] personal; public Company(Person[] people) =>personal = people; // индексатор public Person this[int index] < get =>personal[index]; set => personal[index] = value; > >
Для хранения персонала компании в классе определен массив personal , который состоит из объектов Person. Для доступа к этим объектам определен индексатор:
public Person this[int index]
Индексатор в принципе подобен стандартному свойству. Во-первых, для индексатора определяется тип в данном случае тип Person. Тип индексатора определяет, какие объекты будет получать и возвращать индексатор.
Во-вторых, для индексатора определен параметр int index , через который обращаемся к элементам внутри объекта Company.
Для возвращения объекта в индексаторе определен блок get :
get => personal[index];
Поскольку индексатор имеет тип Person, то в блоке get нам надо возвратить объект этого типа с помощью оператора return. Здесь мы можем определить разнообразную логику. В данном случае просто возвращаем объект из массива personal.
В блоке set , как и в обычном свойстве, получаем через параметр value переданный объект Person и сохраняем его в массив по индексу.
set => personal[index] = value;
После этого мы можем работать с объектом Company как с набором объектов Person:
var microsoft = new Company(new[] < new Person("Tom"), new Person("Bob"), new Person("Sam"), new Person("Alice") >); // получаем объект из индексатора Person firstPerson = microsoft[0]; Console.WriteLine(firstPerson.Name); // Tom // переустанавливаем объект microsoft[0] = new Person("Mike"); Console.WriteLine(microsoft[0].Name); // Mike
Стоит отметить, что если индексатору будет передан некорректный индекс, который отсутствует в массиве person, то мы получим исключение, как и в случае обращения напрямую к элементам массива. В этом случае можно предусмотреть какую-то дополнительную логику. Например, проверять переданный индекс:
class Company < Person[] personal; public Company(Person[] people) =>personal = people; // индексатор public Person this[int index] < get < // если индекс имеется в массиве if (index >= 0 && index < personal.Length) return personal[index]; // то возвращаем объект Person по индексу else throw new ArgumentOutOfRangeException(); // иначе генерируем исключение >set < // если индекс есть в массиве if (index >= 0 && index < personal.Length) personal[index] = value; // переустанавливаем значение по индексу >> >
Здесь в блоке get если переданный индекс имеется в массиве, то возвращаем объект по индексу. Если индекса нет в массиве, то генерируем исключение. Аналогично в блоке set устанавливаем значение по индексу, если индекс есть в массиве.
Индексы
Индексатор получает набор индексов в виде параметров. Однако индексы необязательно должны представлять тип int, устанавливаемые/возвращаемые значения необязательно хранить в массиве. Например, мы можем рассматривать объект как хранилище атрибутов/свойств и передавать имя атрибута в виде строки:
User tom = new User(); // устанавливаем значения tom["name"] = "Tom"; tom["email"] = "tom@gmail.ru"; tom["phone"] = "+1234556767"; // получаем значение Console.WriteLine(tom["name"]); // Tom class User < string name = ""; string email = ""; string phone = ""; public string this[string propname] < get < switch (propname) < case "name": return name; case "email": return email; case "phone": return phone; default: throw new Exception("Unknown Property Name"); >> set < switch (propname) < case "name": name = value; break; case "email": email = value; break; case "phone": phone = value; break; >> > >
В данном случае индексатор в классе User в качестве индекса получает строку, которая хранит название атрибута (в данном случае название поля класса).
В блоке get в зависимости от значения строкового индекса возвращается значение того или иного поля класса. Если передано неизвестное название, то генерируется исключение. В блоке set похожая логика — по индексу узнаем, для какого поля надо установить значение.
Применение нескольких параметров
Также индексатор может принимать несколько параметров. Допустим, у нас есть класс, в котором хранилище определено в виде двухмерного массива или матрицы:
class Matrix < int[,] numbers = new int[,] < < 1, 2, 4 >, < 2, 3, 6 >, < 3, 4, 8 >>; public int this[int i, int j] < get =>numbers[i, j]; set => numbers[i, j] = value; > >
Теперь для определения индексатора используются два индекса — i и j. И в программе мы уже должны обращаться к объекту, используя два индекса:
Matrix matrix = new Matrix(); Console.WriteLine(matrix[0, 0]); matrix[0, 0] = 111; Console.WriteLine(matrix[0, 0]);
Следует учитывать, что индексатор не может быть статическим и применяется только к экземпляру класса. Но при этом индексаторы могут быть виртуальными и абстрактными и могут переопределяться в произодных классах.
Блоки get и set
Как и в свойствах, в индексаторах можно опускать блок get или set, если в них нет необходимости. Например, удалим блок set и сделаем индексатор доступным только для чтения:
class Matrix < int[,] numbers = new int[,] < < 1, 2, 4 >, < 2, 3, 6 >, < 3, 4, 8 >>; public int this[int i, int j] < get =>numbers[i, j]; > >
Также мы можем ограничивать доступ к блокам get и set, используя модификаторы доступа. Например, сделаем блок set приватным:
class Matrix < int[,] numbers = new int[,] < < 1, 2, 4 >, < 2, 3, 6 >, < 3, 4, 8 >>; public int this[int i, int j] < get =>numbers[i, j]; private set => numbers[i, j] = value; > >
Перегрузка индексаторов
Подобно методам индексаторы можно перегружать. В этом случае также индексаторы должны отличаться по количеству, типу или порядку используемых параметров. Например:
var microsoft = new Company(new Person[] < new("Tom"), new("Bob"), new("Sam") >); Console.WriteLine(microsoft[0].Name); // Tom Console.WriteLine(microsoft["Bob"].Name); // Bob class Person < public string Name < get;>public Person(string name) => Name=name; > class Company < Person[] personal; public Company(Person[] people) =>personal = people; // индексатор public Person this[int index] < get =>personal[index]; set => personal[index] = value; > public Person this[string name] < get < foreach (var person in personal) < if (person.Name == name) return person; >throw new Exception("Unknown name"); > > >
В данном случае класс Company содержит две версии индексатора. Первая версия получает и устанавливает объект Person по индексу, а вторая — только получае объект Person по его имени.