Korzystanie z indeksatorów (Przewodnik programowania w języku C#)

Indeksatory to wygoda składniowa, która umożliwia tworzenie klasy, struktury lub interfejsu , do którego aplikacje klienckie mogą uzyskiwać dostęp jako tablica. Kompilator wygeneruje Item właściwość (lub alternatywnie nazwaną właściwość, jeśli IndexerNameAttribute jest obecna) i odpowiednie metody dostępu. Indeksatory są najczęściej implementowane w typach, których głównym celem jest hermetyzacja wewnętrznej kolekcji lub tablicy. Załóżmy na przykład, że masz klasę TempRecord reprezentującą temperaturę w Fahrenheita zarejestrowaną w 10 różnych godzinach w okresie 24-godzinnym. Klasa zawiera tablicę temps typu float[] do przechowywania wartości temperatury. Implementując indeksator w tej klasie, klienci mogą uzyskiwać dostęp do temperatur w TempRecord wystąpieniu jako float temp = tempRecord[4] zamiast jako float temp = tempRecord.temps[4]. Notacja indeksatora nie tylko upraszcza składnię aplikacji klienckich; sprawia również, że klasa i jej cel jest bardziej intuicyjny dla innych deweloperów, aby zrozumieć.

Aby zadeklarować indeksator klasy lub struktury, użyj tego słowa kluczowego, jak pokazano w poniższym przykładzie:

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

Ważne

Deklarowanie indeksatora spowoduje automatyczne wygenerowanie właściwości o nazwie Item w obiekcie. Właściwość Item nie jest dostępna bezpośrednio z wyrażenia dostępu członka wystąpienia. Ponadto w przypadku dodania własnej Item właściwości do obiektu za pomocą indeksatora zostanie wyświetlony błąd kompilatora CS0102. Aby uniknąć tego błędu, użyj nazwy indeksatora IndexerNameAttribute , jak opisano poniżej.

Uwagi

Typ indeksatora i typ jego parametrów musi być co najmniej tak dostępny, jak sam indeksator. Aby uzyskać więcej informacji na temat poziomów ułatwień dostępu, zobacz Modyfikatory dostępu.

Aby uzyskać więcej informacji na temat używania indeksatorów z interfejsem, zobacz Indeksatory interfejsu.

Podpis indeksatora składa się z liczby i typów jego parametrów formalnych. Nie zawiera typu indeksatora ani nazw parametrów formalnych. Jeśli zadeklarowasz więcej niż jeden indeksator w tej samej klasie, musi mieć różne podpisy.

Indeksator nie jest klasyfikowany jako zmienna; dlatego wartość indeksatora nie może być przekazywana przez odwołanie (jako ref parametr lub out ), chyba że jego wartość jest odwołaniem (tj. zwracane przez odwołanie).

Aby podać indeksatorowi nazwę, która może być używana przez inne języki, użyj polecenia System.Runtime.CompilerServices.IndexerNameAttribute, jak pokazano w poniższym przykładzie:

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

Ten indeksator będzie miał nazwę TheItem, ponieważ jest zastępowany przez atrybut nazwy indeksatora. Domyślnie nazwa indeksatora to Item.

Przykład 1

W poniższym przykładzie pokazano, jak zadeklarować pole tablicy prywatnej, tempsi indeksator. Indeksator umożliwia bezpośredni dostęp do wystąpienia tempRecord[i]. Alternatywą dla używania indeksatora jest zadeklarowanie tablicy jako elementu członkowskiego publicznego i bezpośredni dostęp do jego elementów członkowskich. 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;
    }
}

Zwróć uwagę, że po ocenie dostępu indeksatora, na przykład w Console.Write instrukcji, wywoływana jest funkcja get accessor. W związku z tym, jeśli nie get istnieje akcesorium, wystąpi błąd czasu kompilacji.

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

Indeksowanie przy użyciu innych wartości

Język C# nie ogranicza typu parametru indeksatora do liczby całkowitej. Na przykład może być przydatne użycie ciągu z indeksatorem. Taki indeksator może zostać zaimplementowany przez wyszukanie ciągu w kolekcji i zwrócenie odpowiedniej wartości. Ponieważ metody dostępu mogą być przeciążone, wersje ciągów i liczb całkowitych mogą współistnieć.

Przykład 2

Poniższy przykład deklaruje klasę, która przechowuje dni tygodnia. Metoda get dostępu przyjmuje ciąg, nazwę dnia i zwraca odpowiednią liczbę całkowitą. Na przykład wyrażenie "Niedziela" zwraca wartość 0, "poniedziałek" zwraca wartość 1 itd.

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

Korzystanie z przykładu 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}");
}

Przykład 3

Poniższy przykład deklaruje klasę, która przechowuje dni tygodnia przy użyciu wyliczenia System.DayOfWeek . Metoda get dostępu przyjmuje DayOfWeekwartość , wartość dnia i zwraca odpowiednią liczbę całkowitą. Na przykład DayOfWeek.Sunday zwraca wartość 0, DayOfWeek.Monday zwraca wartość 1 itd.

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

Korzystanie z przykładu 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}");
}

Niezawodne programowanie

Istnieją dwa główne sposoby ulepszania zabezpieczeń i niezawodności indeksatorów:

  • Pamiętaj, aby uwzględnić jakiś typ strategii obsługi błędów w celu obsługi prawdopodobieństwa przekazania kodu klienta w nieprawidłowej wartości indeksu. W pierwszym przykładzie we wcześniejszej części tego tematu klasa TempRecord udostępnia właściwość Length, która umożliwia kodowi klienta zweryfikowanie danych wejściowych przed przekazaniem go do indeksatora. Można również umieścić kod obsługi błędów wewnątrz samego indeksatora. Pamiętaj, aby udokumentować dla użytkowników wszelkie wyjątki zgłaszane wewnątrz metody dostępu indeksatora.

  • Ustaw dostępność metod pobierania i ustawiania metod dostępu tak restrykcyjne, jak jest to uzasadnione. Jest to ważne w szczególności dla akcesoriów set . Aby uzyskać więcej informacji, zobacz Ograniczanie ułatwień dostępu dostępu.

Zobacz też