Поделиться через


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

Замечания

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

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

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

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

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

См. также