Содержание
Индексаторы в 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# позволяют использовать объекты как обычные массивы. Используя индексатор, мы можем упростить код и обращаться к внутренней коллекции объекта также, как мы бы обращались к элементам массива по их индексу. Кроме этого, с помощью индексаторов можно добавлять свою логику при получении или записи данных во внутреннее хранилище объекта.