Использование индексаторов. Руководство по программированию на 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, чтобы переименовать индексатор, как описывается ниже.

Замечания

Тип индексатора и типы его параметров должны иметь по крайней мере такой же уровень доступности, как и сам индексатор. Дополнительные сведения об уровнях доступа см. в разделе Модификаторы доступа.

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

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

Индексатор не классифицируется как переменная; Таким образом, значение индексатора невозможно передать по ссылке (как параметру refout ), если его значение не является ссылкой (т. е. возвращается по ссылке.)

Чтобы присвоить индексатору имя, которое можно использовать в других языках, используйте 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 #{i} = {tempRecord[i]}");
}

Индексирование с использованием других значений

В 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 {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: {e.Message}");
}

Пример 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 {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: {e.Message}");
}

Отказоустойчивость

Повысить безопасность и надежность индексаторов можно двумя способами:

  • Реализуйте стратегию обработки ошибок, предусматривающую действия в ситуациях, когда из клиентского кода передается недопустимое значение индекса. В первом примере из этого раздела класс TempRecord содержит свойство Length, с помощью которого клиентский код проверяет введенное значение, прежде чем передать его в индексатор. Кроме того, код обработки ошибок можно поместить в сам индексатор. Не забудьте задокументировать исключения, которые будут вызываться в методе доступа индексатора, для других пользователей.

  • Настройте максимально ограничивающие уровни доступа для методов доступа get и set. Особенно важно сделать это для метода доступа set. Дополнительные сведения см. в разделе Доступность методов доступа.

См. также