Verwenden von Indexern (C#-Programmierhandbuch)

Indexer sind ein syntaktisches Hilfsmittel, um Ihnen die Erstellung einer Klasse, Struktur oder Schnittstelle zu ermöglichen, auf die Clientanwendungen als Array zugreifen können. Der Compiler generiert die Eigenschaft Item (oder eine anders benannte Eigenschaft, wenn IndexerNameAttribute vorhanden ist) und die entsprechenden Zugriffsmethoden. Indexer werden am häufigsten in Typen implementiert, deren Hauptzweck darin besteht, eine interne Auflistung oder ein Array zu kapseln. Angenommen, Sie verfügen beispielsweise über eine Klasse namens TempRecord zur Darstellung der Temperatur in Fahrenheit zu 10 verschiedenen Zeitpunkten während eines 24-stündigen Zeitraums. Die Klasse enthält das Array temps vom Typ float[] zum Speichern der Temperaturwerte. Durch die Implementierung eines Indexers in dieser Klasse können Clients auf die Temperaturen in einer TempRecord-Instanz als float temp = tempRecord[4] zugreifen, statt als float temp = tempRecord.temps[4]. Die Indexernotation vereinfacht nicht nur die Syntax für Clientanwendungen. Sie ermöglicht es auch anderen Entwicklern, die Klasse und deren Zweck intuitiver zu verstehen.

Um einen Indexer in einer Klasse oder Struktur zu deklarieren, verwenden Sie das this -Schlüsselwort, wie im folgenden Beispiel gezeigt:

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

Wichtig

Beim Deklarieren eines Indexers wird automatisch eine Eigenschaft mit dem Namen Item für das Objekt generiert. Auf die Eigenschaft Item kann nicht direkt über den Memberzugriffsausdruck der Instanz zugegriffen werden. Wenn Sie Ihre eigene Eigenschaft Item zu einem Objekt mit einem Indexer hinzufügen, wird außerdem der Compilerfehler CS0102 angezeigt. Verwenden Sie IndexerNameAttribute, und benennen Sie den Indexer wie unten beschrieben um, um diesen Fehler zu vermeiden.

Hinweise

Der Typ eines Indexers und der Typ seiner Parameter müssen zumindest dieselben Zugriffsmöglichkeiten bieten wie der Indexer selbst. Weitere Informationen zu den Zugriffsebenen finden Sie unter Zugriffsmodifizierer.

Weitere Informationen zum Verwenden von Indexern mit einer Schnittstelle finden Sie unter Schnittstellenindexer.

Die Signatur eines Indexers besteht aus der Anzahl und den Typen seiner formalen Parameter. Sie umfasst weder den Indexertyp noch die Namen der formalen Parameter. Wenn Sie mehrere Indexer in derselben Klasse deklarieren, müssen sie verschiedene Signaturen aufweisen.

Ein Indexer wird nicht als Variable klassifiziert. Daher kann ein Indexerwert nicht als Verweis (als ref- oder out-Parameter) übergeben werden, es sei denn, sein Wert ist ein Verweis (d. h. er wird als Verweis zurückgegeben.)

Um für den Indexer einen Namen anzugeben, den andere Sprachen verwenden können, verwenden Sie System.Runtime.CompilerServices.IndexerNameAttribute, wie im folgenden Beispiel gezeigt:

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

Dieser Indexer hat den Namen TheItem, da er vom Indexernamensattribut überschrieben wird. Standardmäßig ist der Indexername Item.

Beispiel 1

Im folgenden Beispiel wird die Deklaration eines öffentlichen Arrayfelds, temps, und eines Indexers dargestellt. Der Indexer ermöglicht den direkten Zugriff auf die Instanz tempRecord[i]. Als Alternative zur Verwendung des Indexers, kann das Array als öffentliches Mitglied deklariert und direkt auf dessen Mitglieder, tempRecord.temps[i], zugegriffen werden.

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;
    }
}

Beachten Sie, dass, wenn der Zugriff eines Indexers, z.B. in einer Console.Write-Anweisung ausgewertet wird, der get-Accessor aufgerufen wird. Wenn kein get-Accessor vorhanden ist, tritt deshalb ein Kompilierzeitfehler auf.

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

Indizieren mit anderen Werten

C# beschränkt den Indexer-Parametertyp nicht auf Integer. Beispielsweise kann es sinnvoll sein, eine Zeichenfolge mit einem Indexer zu verwenden. Ein solcher Indexer kann implementiert werden, indem die Zeichenfolge in der Auflistung gesucht und der richtige Wert zurückgegeben wird. Da Zugriffsmethoden überladen werden können, können die Zeichenfolgen- und die Integer-Version gleichzeitig vorhanden sein.

Beispiel 2

Das folgende Beispiel deklariert eine Klasse, die die Wochentage speichert. Ein get-Accessor akzeptiert eine Zeichenfolge – den Namen eines Tages – und gibt den entsprechenden Integer-Wert zurück. „Sunday“ gibt beispielsweise 0 zurück, „Monday“ 1 usw.

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

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

Beispiel 3

Im folgenden Beispiel wird eine Klasse deklariert, die die Wochentage mithilfe der Enumeration System.DayOfWeek speichert. Eine get-Zugriffsmethode akzeptiert DayOfWeek (den Wert für einen Tag) und gibt den entsprechenden Integer-Wert zurück. Bei DayOfWeek.Sunday wird beispielsweise 0 zurückgegeben, bei DayOfWeek.Monday 1 und so weiter.

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

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

Stabile Programmierung

Es gibt zwei grundlegende Möglichkeiten, mit denen die Sicherheit und die Zuverlässigkeit von Indexern verbessert werden kann:

  • Integrieren Sie irgendeine Form von Fehlerbehandlungsstrategie, für den Fall, dass Clientcode in einem ungültigen Indexwert übergeben wird. Im ersten Beispiel weiter oben in diesem Thema stellt die TempRecord-Klasse eine Length-Eigenschaft bereit, die dem Clientcode ermöglicht, die Eingabe vor der Übergabe an den Indexer zu überprüfen. Sie können den Fehlerbehandlungscode auch in den Indexer selbst einfügen. Achten Sie darauf alle Ausnahmen, die Sie innerhalb eines Indexeraccessors auslösen, für Benutzer zu dokumentieren.

  • Schränken Sie den Zugriff auf die get- und set-Accessors so weit wie möglich ein. Dies ist insbesondere wichtig für den set-Accessor. Weitere Informationen finden Sie unter Einschränken des Accessorzugriffs.

Weitere Informationen