Использование индексаторов. Руководство по программированию на C#Using indexers (C# Programming Guide)

Применение индексаторов упрощает работу с синтаксисом, позволяя создавать классы, структуры и интерфейсы, к которым клиентские приложения могут обращаться так же, как к массиву.Indexers are a syntactic convenience that enable you to create a class, struct, or interface that client applications can access as an array. В этом случае компилятор создает свойство Item (или свойство с другим именем, если присутствует IndexerNameAttribute) и соответствующие методы доступа.The compiler will generate an Item property (or an alternatively named property if IndexerNameAttribute is present), and the appropriate accessor methods. Индексаторы чаще всего реализуются в типах, предназначенных преимущественно для инкапсуляции внутренней коллекции или массива.Indexers are most frequently implemented in types whose primary purpose is to encapsulate an internal collection or array. Допустим, у вас есть класс TempRecord, представляющий журнал с 10 измерениями температуры по шкале Фаренгейта за период в 24 часа.For example, suppose you have a class TempRecord that represents the temperature in Fahrenheit as recorded at 10 different times during a 24-hour period. Этот класс содержит массив temps типа float[] для хранения значений температуры.The class contains a temps array of type float[] to store the temperature values. Реализация индексатора в этом классе позволит клиентам получать доступ к значениям температуры в экземпляре TempRecord, используя float temp = tempRecord[4] вместо float temp = tempRecord.temps[4].By implementing an indexer in this class, clients can access the temperatures in a TempRecord instance as float temp = tempRecord[4] instead of as float temp = tempRecord.temps[4]. Это позволяет не только упростить синтаксис клиентских приложений, но и облегчить понимание кода класса и его предназначения другими разработчиками.The indexer notation not only simplifies the syntax for client applications; it also makes the class, and its purpose more intuitive for other developers to understand.

Чтобы объявить индексатор для класса или структуры, используйте ключевое слово this, как в следующем примере:To declare an indexer on a class or struct, use the this keyword, as the following example shows:

// Indexer declaration
public int this[int index]
{
    // get and set accessors
}

Важно!

При объявлении индексатора для объекта автоматически создается свойство с именем Item.Declaring an indexer will automatically generate a property named Item on the object. Свойство Item не будет доступно непосредственно из выражения доступа к члену экземпляра.The Item property is not directly accessible from the instance member access expression. Кроме того, если вы добавите к объекту с индексатором собственное свойство Item, возникнет ошибка компилятора CS0102.Additionally, if you add your own Item property to an object with an indexer, you'll get a CS0102 compiler error. Чтобы избежать этого, используйте IndexerNameAttribute, чтобы переименовать индексатор, как описывается ниже.To avoid this error, use the IndexerNameAttribute rename the indexer as detailed below.

ПримечанияRemarks

Тип индексатора и типы его параметров должны иметь по крайней мере такой же уровень доступности, как и сам индексатор.The type of an indexer and the type of its parameters must be at least as accessible as the indexer itself. Дополнительные сведения об уровнях доступа см. в разделе Модификаторы доступа.For more information about accessibility levels, see Access Modifiers.

Дополнительные сведения об использовании индексаторов с интерфейсом см. в разделе Индексаторы интерфейса.For more information about how to use indexers with an interface, see Interface Indexers.

Сигнатура индексатора определяет число и типы его формальных параметров.The signature of an indexer consists of the number and types of its formal parameters. В ней не указываются тип индексатора или имена его формальных параметров.It doesn't include the indexer type or the names of the formal parameters. Если для одного класса объявляется несколько индексаторов, они должны иметь разные сигнатуры.If you declare more than one indexer in the same class, they must have different signatures.

Значение индексатора не классифицируется как переменная и, соответственно, не может передаваться в качестве параметра ref или out.An indexer value is not classified as a variable; therefore, you cannot pass an indexer value as a ref or out parameter.

Чтобы присвоить индексатору имя, которое можно использовать в других языках, используйте System.Runtime.CompilerServices.IndexerNameAttribute, как показано в этом примере:To provide the indexer with a name that other languages can use, use System.Runtime.CompilerServices.IndexerNameAttribute, as the following example shows:

// Indexer declaration
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
    // get and set accessors
}

Этот индексатор будет иметь имя TheItem, поскольку оно переопределено атрибутом имени индексатора.This indexer will have the name TheItem, as it is overridden by the indexer name attribute. По умолчанию используется имя индексатора Item.By default, the indexer name is Item.

Пример 1Example 1

В следующем примере показано, как объявить частное поле массива temps и индексатор.The following example shows how to declare a private array field, temps, and an indexer. Индексатор обеспечивает прямой доступ к экземпляру tempRecord[i].The indexer enables direct access to the instance tempRecord[i]. Вместо использования индексатора можно объявить массив как элемент public и осуществлять доступ к его элементам напрямую (tempRecord.temps[i]).The alternative to using the indexer is to declare the array as a public member and access its members, tempRecord.temps[i], directly.

public class TempRecord
{
    // Array of temperature values
    float[] temps = new float[10]
    {
        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.Notice that when an indexer's access is evaluated, for example, in a Console.Write statement, the get accessor is invoked. Таким образом, если метод доступа get отсутствует, возникает ошибка времени компиляции.Therefore, if no get accessor exists, a compile-time error occurs.

using System;

class Program
{
    static void Main()
    {
        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]}");
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
    /* Output:
        Element #0 = 56.2
        Element #1 = 56.7
        Element #2 = 56.5
        Element #3 = 58.3
        Element #4 = 58.8
        Element #5 = 60.1
        Element #6 = 65.9
        Element #7 = 62.1
        Element #8 = 59.2
        Element #9 = 57.5
    */
}

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

В C# тип параметра индексатора не ограничивается целочисленными значениями.C# doesn't limit the indexer parameter type to integer. Например, в качестве индексатора могут использоваться строки.For example, it may be useful to use a string with an indexer. Такой индексатор можно реализовать путем поиска строки в коллекции с возвратом соответствующего значения.Such an indexer might be implemented by searching for the string in the collection, and returning the appropriate value. Поскольку методы доступа можно перегружать, строковые и целочисленные версии могут сосуществовать.As accessors can be overloaded, the string and integer versions can coexist.

Пример 2Example 2

Этот пример объявляет класс, который хранит названия дней недели.The following example declares a class that stores the days of the week. Метод доступа get принимает название дня в виде строкового значения и возвращает соответствующее целое число.A get accessor takes a string, the name of a day, and returns the corresponding integer. Например, для Sunday возвращается значение 0, для Monday — 1 и т. д.For example, "Sunday" returns 0, "Monday" returns 1, and so on.

using System;

// 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");
    }
}

Пример использования 2Consuming example 2

using System;

class Program
{
    static void Main(string[] args)
    {
        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}");
        }
    }
    // Output:
    // 5
    // Not supported input: Day Made-up day is not supported.
    // Day input must be in the form "Sun", "Mon", etc (Parameter 'day')
}

Пример 3Example 3

В этом примере объявляется класс, в котором хранятся названия дней недели с использованием перечисления System.DayOfWeek.The following example declares a class that stores the days of the week using the System.DayOfWeek enum. Метод доступа get принимает название дня (DayOfWeek) в виде строкового значения и возвращает соответствующее целое число.A get accessor takes a DayOfWeek, the value of a day, and returns the corresponding integer. Например, для DayOfWeek.Sunday возвращается 0, для DayOfWeek.Monday — 1 и т. д.For example, DayOfWeek.Sunday returns 0, DayOfWeek.Monday returns 1, and so on.

using System;
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.");
    }
}

Пример использования 3Consuming example 3

using System;

class Program
{
    static void Main()
    {
        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}");
        }
    }
    // Output:
    // 5
    // Not supported input: Day 43 is not supported.
    // Day input must be a defined System.DayOfWeek value. (Parameter 'day')
}

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

Повысить безопасность и надежность индексаторов можно двумя способами:There are two main ways in which the security and reliability of indexers can be improved:

  • Реализуйте стратегию обработки ошибок, предусматривающую действия в ситуациях, когда из клиентского кода передается недопустимое значение индекса.Be sure to incorporate some type of error-handling strategy to handle the chance of client code passing in an invalid index value. В первом примере из этого раздела класс TempRecord содержит свойство Length, с помощью которого клиентский код проверяет введенное значение, прежде чем передать его в индексатор.In the first example earlier in this topic, the TempRecord class provides a Length property that enables the client code to verify the input before passing it to the indexer. Кроме того, код обработки ошибок можно поместить в сам индексатор.You can also put the error handling code inside the indexer itself. Не забудьте задокументировать исключения, которые будут вызываться в методе доступа индексатора, для других пользователей.Be sure to document for users any exceptions that you throw inside an indexer accessor.

  • Настройте максимально ограничивающие уровни доступа для методов доступа get и set.Set the accessibility of the get and set accessors to be as restrictive as is reasonable. Особенно важно сделать это для метода доступа set.This is important for the set accessor in particular. Дополнительные сведения см. в разделе Доступность методов доступа.For more information, see Restricting Accessor Accessibility.

См. такжеSee also