Классы и объекты C#: индексаторы

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Индексаторы в 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] представляет собой индексатор.  Здесь определяем:

  1. тип возвращаемого или присваиваемого объекта (Person).
  2. способ доступа к элементам  (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.

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

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
guest
0 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии