Содержание
Индексаторы в C# позволяют индексировать объекты и обращаться к данным по индексу точно также, как в массивах. По своей форме индексатор поход на свойство со стандартными блоками get
и set
, которые возвращают и присваивают значение. Сегодня разберемся с тем, как используются индексаторы в C#.
Формальное определение индексатора в C# выглядит следующим образом:
возвращаемый_тип this [Тип параметр1, ...] { get { ... } set { ... } }
При этом, в отличие от свойств класса индексатор не имеет названия. Вместо него указывается ключевое слово this
, после которого в квадратных скобках указываются параметры. Индексатор должен иметь минимум один параметр. Рассмотрим работу индексатора на примере.
Пример индексатора в C#
Допустим, у нас имеется класс Person
, представляющий человека, и класс People
, который представляет группу людей. Используем индексаторы для определения класса People
:
//человек class Person { public string Name { get; set; } } //группа людей class People { Person[] data; public People() { data = new Person[5]; } // индексатор public Person this[int index] { get { return data[index]; } set { data[index] = value; } } }
Конструкция public Person this[int index]
представляет собой индексатор. Здесь определяем:
- тип возвращаемого или присваиваемого объекта (
Person
). - способ доступа к элементам (
int index
).
Так как в классе People
все объекты Person
хранятся в массиве data
, то для получения их по индексу в индексаторе определен блок get
:
get { return data[index]; }
Аналогично и с присваиванием значения. В блоке set
мы получаем через параметр value
переданный объект Person
и сохраняем его в массив по индексу.
People
практически также, как и с массивом объектов Person
:People people = new People(); //добавляем новых людей people[0] = new Person { Name = "Вася" }; people[1] = new Person { Name = "Петя" }; //получаем элемент из data Person vasya = people[0]; Console.WriteLine(vasya?.Name); Console.ReadKey();
При выводе имени человека в консоль мы использовали оператор условного null.
int
. Например, можно рассматривать любой объект как хранилище свойств и передавать индексатору имя атрибута объекта в виде строки:class User { string name; string email; string phone; public string this[string propname] { get { switch (propname) { case "name": return "Mr/Ms. " + name; case "email": return email; case "phone": return phone; default: return null; } } set { switch (propname) { case "name": name = value; break; case "email": email = value; break; case "phone": phone = value; break; } } } } class Program { static void Main(string[] args) { User tom = new User(); tom["name"] = "Tom"; tom["email"] = "tomekvilmovskiy@gmail.ru"; Console.WriteLine(tom["name"]); // Mr/Ms. Tom Console.ReadKey(); } }
Применение нескольких параметров в индексаторе C#
Индексатор может принимать несколько параметров. Допустим, у нас имеется класс, в котором хранение данных определено в виде двухмерного массива:
class Matrix { private int[,] numbers = new int[,] { { 1, 2, 4}, { 2, 3, 6 }, { 3, 4, 8 } }; public int this[int i, int j] { get { return 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 индексаторов C#
Как и в свойствах классов, в индексаторах можно не указывать блоки get
или set
, если в них нет необходимости. Например, удалим блок set
и сделаем индексатор доступным только для чтения:
class Matrix { private int[,] numbers = new int[,] { { 1, 2, 4}, { 2, 3, 6 }, { 3, 4, 8 } }; public int this[int i, int j] { get { return numbers[i,j]; } } }
Также можно ограничивать доступ к блокам get
и set
, используя модификаторы доступа. Например, в следующем примере блок set
является приватным:
class Matrix { private int[,] numbers = new int[,] { { 1, 2, 4}, { 2, 3, 6 }, { 3, 4, 8 } }; public int this[int i, int j] { get { return numbers[i,j]; } private set { numbers[i, j] = value; } } }
Перегрузка индексаторов
Подобно методам индексаторы можно перегружать. В этом случае также индексаторы должны отличаться по количеству, типу или порядку используемых параметров. Например:
class Person { public string Name { get; set; } public int Age { get; set; } } class People { Person[] data; public People() { data = new Person[5]; } public Person this[int index] { get { return data[index]; } set { data[index] = value; } } public Person this[string name] { get { Person person = null; foreach(var p in data) { if(p?.Name == name) { person = p; break; } } return person; } } } class Program { static void Main(string[] args) { People people = new People(); people[0] = new Person { Name = "Tom" }; people[1] = new Person { Name = "Bob" }; Console.WriteLine(people[0].Name); // Tom Console.WriteLine(people["Bob"].Name); // Bob Console.ReadKey(); } }
В данном случае класс People
содержит две версии индексатора. Первая версия получает и устанавливает объект Person
по индексу, а вторая — только получает объект Person
по его имени.
Итого
Индексаторы по своему описанию очень походи на свойства классов, но, при этом, не имеют названия (имени). Индексаторы в C# позволяют использовать объекты как обычные массивы. Используя индексатор, мы можем упростить код и обращаться к внутренней коллекции объекта также, как мы бы обращались к элементам массива по их индексу. Кроме этого, с помощью индексаторов можно добавлять свою логику при получении или записи данных во внутреннее хранилище объекта.