Перейти к содержимому

Зачем нужны индексаторы c

  • автор:

Индексаторы (Руководство по программированию в 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

Индексаторы в C#

В этой статье я расскажу о том, что такое индексаторы, зачем они нужны, как и когда ими пользоваться в своих программах, написанных на языке C#. И так, индексаторы нужны в тех случаях, когда мы пишем класс, ведущий себя как некая коллекция, например, в нем есть поле-массив, например, такой класс:

//Класс-коллекция целых чисел class SomeCollection < //Конструктор, создает объект коллекцию, с указанием её размера public SomeCollection(int aSize) < collection = new int[aSize]; >//Свойство, возвращающее размер коллекции (только для чтения) public int Size < get < return collection.Length; >> //Поле-массив, для хранения элементов коллекции private int[] collection; >

Как видно из примера, в классе есть поле-массив, есть конструктор, который задает размер массива, и свойство, возвращающее размер массива «внешнему миру». Но нужно же еще как-то организовать доступ к элементам массива, чтобы пользователи нашего класса могли изменять и получать содержимое внутреннего массива. Тут конечно можно написать пару методов, один будет возвращать значение элемента с указанным, в качестве аргумента метода, индексом, а второй метод, будет устанавливать значение указанного (тем же способом) элемента. И выглядело бы это примерно так:

//Клсаа-коллекция целых чисел class SomeCollection < //Конструктор, создает объект коллекцию, с указанием её размера public SomeCollection(int aSize) < collection = new int[aSize]; >//Метод, возвращающий значение элемента, по указанному индексу public int GetElem(int anIndex) < return collection[anIndex]; >//Метод, устанавливающий значение элемента, по указанному индексу public void SetElem(int aValue, int anIndex) < collection[anIndex] = aValue; >//Свойство, возвращающее размер коллекции (только для чтения) public int Size < get < return collection.Length; >> //Поле-массив, для хранения элементов коллекции private int[] collection; >

А использование объектов данного класса могло бы выглядеть так:

//Создать коллекцию SomeCollection tmpColl = new SomeCollection(20); //Перебрать все элементы коллекции for (int i = 0; i < tmpColl.Size; i++) < //Установить значение элементу с индексом i tmpColl.SetElem(i + 1, i); >//Перебрать все элементы коллекции for (int i = 0; i < tmpColl.Size; i++) < //Вывести в консоль элемент коллекции с индексом i Console.Write(tmpColl.GetElem(i) + " "); >//Перевод строки Console.WriteLine("");

Можете создать новый проект, и проверить это дело на практике… Но можно решить задачу организации доступа к элементам встроенного в объект массива гораздо элегантнее! Вот тут мы и будем использовать индексаторы. Давайте добавим в наш класс индексатор, а выглядеть он будет так:

/* * Индексатор (похож на свойство, но со специфическим именем и * дополнительным параметром, указанным в квадратных скобках) */ public int this[int anIndex] < //Метод доступа get get < return collection[anIndex]; >//Метод доступа set set < collection[anIndex] = value; >>

Как видите, индексатор очень похож на свойство, но у него должно быть специфическое имя «this» и дополнительный параметр в квадратных скобках (которого нет у обычного свойства).

Вообще this — это ключевое слово языка C# и оно имеет особое значение (используется не только в индексаторах), о котором я как-нибудь расскажу в одной из статей.

Этот дополнительный параметр используется как индекс, для обращения к внутреннему массиву. Использование объектов класса, в который мы добавили индексатор выглядит как использование массивов:

//Создать коллекцию SomeCollection tmpColl = new SomeCollection(20); //Перебрать все элементы коллекции for (int i = 0; i < tmpColl.Size; i++) < //Установить значение элементу с индексом i tmpColl[i] = i + 1; >//Перебрать все элементы коллекции for (int i = 0; i < tmpColl.Size; i++) < //Вывести в консоль элемент коллекции с индексом i Console.Write(tmpColl[i] + " "); >//Перевод строки Console.WriteLine("");

Согласитесь, этот вариант короче и удобнее! Ну пока хватит информации об индексаторах, как-нибудь я подробнее рассажу о них…

Добавить комментарий Отменить ответ

Для отправки комментария вам необходимо авторизоваться.

Зачем нужны индексаторы 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 по его имени.

Индексаторы

Программисты хорошо знакомы с процессом доступа к индивидуальным элементам, содержащимся в стандартных массивах, через операцию индекса ([]). В C# имеется возможность проектировать специальные классы и структуры, которые могут быть индексированы подобно стандартному массиву, посредством определения . Это конкретное языковое средство наиболее полезно при создании специальных типов коллекций (обобщенных и необобщенных). Индексаторы могут быть одно- или многомерными.

Одномерные индексаторы

Ниже приведена общая форма одномерного индексатора:

тип_элемента this[int индекс] < // Аксессор для получения данных, get < // Возврат значения, которое определяет индекс. >// Аксессор для установки данных, set < // Установка значения, которое определяет индекс. >>

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

В теле индексатора определены два аксессора (т.е. средства доступа к данным): get и set. Аксессор подобен методу, за исключением того, что в нем не объявляется тип возвращаемого значения или параметры. Аксессоры вызываются автоматически при использовании индексатора, и оба получают индекс в качестве параметра. Так, если индексатор указывается в левой части оператора присваивания, то вызывается аксессор set и устанавливается элемент, на который указывает параметр индекс. В противном случае вызывается аксессор get и возвращается значение, соответствующее параметру индекс. Кроме того, аксессор set получает неявный параметр value, содержащий значение, присваиваемое по указанному индексу.

Давайте рассмотрим пример:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 < class MyArr < int[] arr; public int Length; public MyArr(int Size) < arr = new int[Size]; Length = Size; >// Создаем простейший индексатор public int this[int index] < set < arr[index] = value; >get < return arr[index]; >> > class Program < static void Main() < MyArr arr1 = new MyArr(Size: 5); Random ran = new Random(); // Инициализируем каждый индекс экземпляра класса arr1 for (int i = 0; i < arr1.Length; i++) < arr1[i] = ran.Next(1,100); Console.Write("\t", arr1[i]); > Console.ReadLine(); > > >

В текущем классе MyArr определен индексатор, позволяющий вызывающему коду идентифицировать подэлементы с применением числовых значений. Однако надо понимать, что это не обязательное требование метода-индексатора.

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

На применение индексаторов накладываются два существенных ограничения. Во-первых, значение, выдаваемое индексатором, нельзя передавать методу в качестве параметра ref или out, поскольку в индексаторе не определено место в памяти для его хранения. И во-вторых, индексатор должен быть членом своего класса и поэтому не может быть объявлен как static.

Многомерные индексаторы

Можно также создавать индексатор, принимающий несколько параметров:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 < class MyArr < int[,] arr; // Размерность двухмерного массива public int rows, cols; public int Length; public MyArr(int rows, int cols) < this.rows = rows; this.cols = cols; arr = new int[this.rows, this.cols]; Length = rows * cols; >// Индексатор public int this[int index1, int index2] < get < return arr[index1, index2]; >set < arr[index1, index2] = value; >> > class Program < static void Main(string[] args) < Random ran = new Random(); Console.WriteLine("Arr1: \n"); MyArr arr1 = new MyArr(4,5); for (int i = 0; i < arr1.rows - 1; i++) < for (int j = 0; j < arr1.cols - 1; j++) < arr1[i, j] = ran.Next(1,20); Console.Write(arr1[i, j] + "\t"); >Console.WriteLine(); > Console.ReadLine(); > > > 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *