IndexerIndexers

Indexer sind ähnlich wie Eigenschaften.Indexers are similar to properties. In vielen Aspekten bauen Indexer genauso wie Eigenschaften auf den gleichen Sprachfunktionen auf.In many ways indexers build on the same language features as properties. Indexer ermöglichen indizierte Eigenschaften: Eigenschaften, auf die von mindestens einem Argument verwiesen wird.Indexers enable indexed properties: properties referenced using one or more arguments. Diese Argumente stellen einen Index in einer Auflistung von Werten bereit.Those arguments provide an index into some collection of values.

IndexersyntaxIndexer Syntax

Sie können mithilfe eines Variablennamens und eckiger Klammern auf einen Indexer zugreifen.You access an indexer through a variable name and square brackets . Geben Sie die Indexerargumente innerhalb der Klammern ein:You place the indexer arguments inside the brackets:

var item = someObject["key"];
someObject["AnotherKey"] = item;

Sie können Indexer deklarieren, in dem sie das Schlüsselwort this als Eigenschaftenname verwenden und die Argumente in eckigen Klammern deklarieren.You declare indexers using the this keyword as the property name, and declaring the arguments within square brackets. Diese Deklaration stimmt mit dem Gebrauch in oben stehendem Abschnitt überein:This declaration would match the usage shown in the previous paragraph:

public int this[string key]
{
    get { return storage.Find(key); }
    set { storage.SetAt(key, value); }
}

Dieses erste Beispiel veranschaulicht die Beziehung zwischen der Syntax der Eigenschaften und der Indexer.From this initial example, you can see the relationship between the syntax for properties and for indexers. Diese Analogie wirkt sich auf die meisten Syntaxregeln für Indexer auf.This analogy carries through most of the syntax rules for indexers. Indexer können keine gültigen Zugriffsmodifizierer haben (öffentliche, geschützte interne, geschützte, interne, private oder private geschützte).Indexers can have any valid access modifiers (public, protected internal, protected, internal, private or private protected). Sie können versiegelt, virtuell oder abstrakt sein.They may be sealed, virtual, or abstract. Bei Eigenschaften können Sie verschiedene Zugriffsmodifizierer für die get- und set-Zugriffsmethoden eines Indexers angeben.As with properties, you can specify different access modifiers for the get and set accesssors in an indexer. Außerdem können Sie schreibgeschützte Indexer (indem Sie die set-Zugriffsmethode auslassen) oder lesegeschützte Indexer (indem Sie die get-Zugriffsmethode auslassen) angeben.You may also specify read-only indexers (by omitting the set accessor), or write-only indexers (by omitting the get accessor).

Fast alles, was Sie von der Arbeit mit Eigenschaften kennen, können sie auch für Indexer anwenden.You can apply almost everything you learn from working with properties to indexers. Die einzige Ausnahme von dieser Regel sind automatisch implementierte Eigenschaften.The only exception to that rule is auto implemented properties. Der Compiler kann nicht immer angemessenen Speicher für einen Indexer generieren.The compiler cannot always generate the correct storage for an indexer.

Indexer unterscheiden sich durch das Vorhandensein von Argumenten, die auf ein Element in einem Satz von Elementen verweisen, von Eigenschaften.The presence of arguments to reference an item in a set of items distinguishes indexers from properties. Sie können mehrere Indexer in einem Typ definieren, solange die Argumentauflistung für jeden Indexer eindeutig ist.You may define multiple indexers on a type, as long as the argument lists for each indexer is unique. In den folgenden Szenarios erfahren Sie mehr über das Verwenden von einem oder mehreren Indexern in einer Klassendefinition.Let's explore different scenarios where you might use one or more indexers in a class definition.

SzenarienScenarios

Sie definieren Indexer in Ihrem Typ, wenn dessen API eine Auflistung modelliert, in der Sie die Argumente der Auflistung definieren.You would define indexers in your type when its API models some collection where you define the arguments to that collection. Ihre Indexer ordnen möglicherweise direkt zu Ihren Auflistungstypen zu, die Teil des .NET Core Frameworks sind.Your indexers may or may not map directly to the collection types that are part of the .NET core framework. Ihr Typ hat möglicherweise andere Verpflichtungen als nur das Modellieren einer Auflistung.Your type may have other responsibilities in addition to modeling a collection. Mit Indexern können Sie die API bereitstellen, die mit der Abstraktion Ihres Typs übereinstimmt, ohne die inneren Details offen zu legen, wie die Werte dieser Abstraktion gespeichert oder berechnet werden.Indexers enable you to provide the API that matches your type's abstraction without exposing the inner details of how the values for that abstraction are stored or computed.

Die häufigsten Szenarios für das Verwenden von Indexern.Let's walk through some of the common scenarios for using indexers. Sie können auf den Beispielordner für Indexer zugreifen.You can access the sample folder for indexers. Anweisungen zum Herunterladen finden Sie unter Beispiele und Lernprogramme.For download instructions, see Samples and Tutorials.

Arrays und VektorenArrays and Vectors

Eines der häufigsten Szenarios beim Erstellen von Indexern ist, wenn Ihr Typ ein Array oder einen Vektor modelliert.One of the most common scenarios for creating indexers is when your type models an array, or a vector. Sie können einen Indexer erstellen, um eine geordnete Datenliste zu modellieren.You can create an indexer to model an ordered list of data.

Der Vorteil beim Erstellen eines eigenen Indexers ist die Tatsache, dass Sie den Speicher für diese Auflistung an Ihre Bedürfnisse anpassen können.The advantage of creating your own indexer is that you can define the storage for that collection to suit your needs. Stellen Sie sich ein Szenario vor, in dem Ihr Typ historische Daten modelliert, die zu groß sind, um sie auf einmal in den Speicher zu laden.Imagine a scenario where your type models historical data that is too large to load into memory at once. Sie müssen Abschnitte der Auflistung nach Verbrauch laden und entladen.You need to load and unload sections of the collection based on usage. Das folgende Beispiel veranschaulicht dieses Verhalten.The example following models this behavior. Es meldet, wie viele Datenpunkte vorhanden sind.It reports on how many data points exist. Es erstellt Seiten, die auf Abruf Abschnitte der Daten anzeigen.It creates pages to hold sections of the data on demand. Es entfernt Seiten aus dem Speicher, um Platz für Seiten zu schaffen, die für eine aktuelle Abfrage benötigt werden.It removes pages from memory to make room for pages needed by more recent requests.

public class DataSamples
{
    private class Page
    {
        private readonly List<Measurements> pageData = new List<Measurements>();
        private readonly int startingIndex;
        private readonly int length;
        private bool dirty;
        private DateTime lastAccess;

        public Page(int startingIndex, int length)
        {
            this.startingIndex = startingIndex;
            this.length = length;
            lastAccess = DateTime.Now;

            // This stays as random stuff:
            var generator = new Random();
            for(int i=0; i < length; i++)
            {
                var m = new Measurements
                {
                    HiTemp = generator.Next(50, 95),
                    LoTemp = generator.Next(12, 49),
                    AirPressure = 28.0 + generator.NextDouble() * 4
                };
                pageData.Add(m);
            }
        }
        public bool HasItem(int index) =>
            ((index >= startingIndex) &&
            (index < startingIndex + length));

        public Measurements this[int index]
        {
            get
            {
                lastAccess = DateTime.Now;
                return pageData[index - startingIndex];
            }
            set
            {
                pageData[index - startingIndex] = value;
                dirty = true;
                lastAccess = DateTime.Now;
            }
        }

        public bool Dirty => dirty;
        public DateTime LastAccess => lastAccess;
    }

    private readonly int totalSize;
    private readonly List<Page> pagesInMemory = new List<Page>();

    public DataSamples(int totalSize)
    {
        this.totalSize = totalSize;
    }

    public Measurements this[int index]
    {
        get
        {
            if (index < 0)
                throw new IndexOutOfRangeException("Cannot index less than 0");
            if (index >= totalSize)
                throw new IndexOutOfRangeException("Cannot index past the end of storage");

            var page = updateCachedPagesForAccess(index);
            return page[index];
        }
        set
        {
            if (index < 0)
                throw new IndexOutOfRangeException("Cannot index less than 0");
            if (index >= totalSize)
                throw new IndexOutOfRangeException("Cannot index past the end of storage");
            var page = updateCachedPagesForAccess(index);

            page[index] = value;
        }
    }

    private Page updateCachedPagesForAccess(int index)
    {
        foreach (var p in pagesInMemory)
        {
            if (p.HasItem(index))
            {
                return p;
            }
        }
        var startingIndex = (index / 1000) * 1000;
        var newPage = new Page(startingIndex, 1000);
        addPageToCache(newPage);
        return newPage;
    }

    private void addPageToCache(Page p)
    {
        if (pagesInMemory.Count > 4)
        {
            // remove oldest non-dirty page:
            var oldest = pagesInMemory
                .Where(page => !page.Dirty)
                .OrderBy(page => page.LastAccess)
                .FirstOrDefault();
            // Note that this may keep more than 5 pages in memory
            // if too much is dirty
            if (oldest != null)
                pagesInMemory.Remove(oldest);
        }
        pagesInMemory.Add(p);
    }
}

Sie können diesem Entwurfsausdruck folgen, um jede beliebige Auflistungsart dort zu modellieren, wo es gute Gründe gibt, nicht den gesamten Datensatz in eine Auflistung im Speicher zu laden.You can follow this design idiom to model any sort of collection where there are good reasons not to load the entire set of data into an in- memory collection. Beachten Sie, dass die Klasse Page eine private geschachtelte Klasse ist, die nicht Teil der öffentlichen Schnittstelle ist.Notice that the Page class is a private nested class that is not part of the public interface. Diese Informationen sind für jeden Benutzer der Klasse verborgen.Those details are hidden from any users of this class.

WörterbücherDictionaries

Ein weiteres häufiges Szenario ist, wenn Sie ein Wörterbuch oder eine Zuordnung modellieren möchten.Another common scenario is when you need to model a dictionary or a map. In diesem Szenario speichert Ihr Typ Werte nach Schlüsseln, typischerweise Textschlüssel.This scenario is when your type stores values based on key, typically text keys. In diesem Beispiel wird ein Wörterbuch erstellt, das Befehlszeilenargumente Lambdaausdrücken zuordnet, die diese Optionen verwalten.This example creates a dictionary that maps command line arguments to lamdba expressions that manage those options. In folgenden Beispiel werden zwei Klassen gezeigt: eine ArgsActions-Klasse, die eine Befehlszeilenoption einem Action-Delegaten zuordnet, und ein ArgsProcessor, um mit jeder ArgsActions eine Action auszuführen, wenn es auf diese Option trifft.The following example shows two classes: an ArgsActions class that maps a command line option to an Action delegate, and an ArgsProcessor that uses the ArgsActions to execute each Action when it encounters that option.

public class ArgsProcessor
{
    private readonly ArgsActions actions;

    public ArgsProcessor(ArgsActions actions)
    {
        this.actions = actions;
    }

    public void Process(string[] args)
    {
        foreach(var arg in args)
        {
            actions[arg]?.Invoke();
        }
    }

}
public class ArgsActions
{
    readonly private Dictionary<string, Action> argsActions = new Dictionary<string, Action>();

    public Action this[string s]
    {
        get
        {
            Action action;
            Action defaultAction = () => {} ;
            return argsActions.TryGetValue(s, out action) ? action : defaultAction;
        }
    }

    public void SetOption(string s, Action a)
    {
        argsActions[s] = a;
    }
}

In diesem Beispiel ordnet die ArgsAction-Auflistung nah an der zugrundeliegenden Auflistung zu.In this example, the ArgsAction collection maps closely to the underlying collection. get legt fest, ob eine gegebene Option konfiguriert wurde.The get determines if a given option has been configured. Falls dem so ist, gibt es die mit dieser Option verknüpfte Action zurück.If so, it returns the Action associated with that option. Falls dem nicht so ist, gibt es eine Action zurück, die keine Auswirkungen hat.If not, it returns an Action that does nothing. Die öffentliche Zugriffsmethode enthält keine set-Zugriffsmethode.The public accessor does not include a set accessor. Stattdessen enthält Sie den Entwurf, der eine öffentliche Methode für das Festlegen von Optionen verwendet.Rather, the design using a public method for setting options.

Mehrdimensionale ZuordnungenMulti-Dimensional Maps

Sie können Indexer erstellen, die mehrere Argumente verwenden.You can create indexers that use multiple arguments. Zusätzlich sind müssen diese Argumente nicht denselben Typen aufweisen.In addition, those arguments are not constrained to be the same type. Nachfolgend sehen Sie zwei Beispiele.Let's look at two examples.

Das erste Beispiel zeigt eine Klasse, die Werte einer Mandelbrot-Menge generiert.The first example shows a class that generates values for a Mandelbrot set. Weitere Informationen zur Mathematik der Menge finden Sie in diesem Artikel.For more information on the mathematics behind the set, read this article. Der Indexer verwendet zwei double-Werte, um einen Punkt auf der X-Y-Ebene zu definieren.The indexer uses two doubles to define a point in the X, Y plane. Die get-Zugriffsmethode berechnet die Anzahl der Iterationen, bis ein Punkt festgelegt wird, der außerhalb der Menge liegt.The get accessor computes the number of iterations until a point is determined to be not in the set. Wenn die maximale Zahl an Iterationen erreicht ist, befindet sich der Punkt in der Menge, und der Wert „maxIterations“ der Klasse wird zurückgegeben.If the maximum iterations is reached, the point is in the set, and the class's maxIterations value is returned. (Die vom Computer generierten Bilder, die durch die Mandelbrot-Menge bekannt wurden, definieren Farben für die Anzahl von Iterationen, die benötigt werden, um festzustellen, das sich ein Punkt außerhalb der Menge befindet.)(The computer generated images popularized for the Mandelbrot set define colors for the number of iterations necessary to determine that a point is outside the set.

public class Mandelbrot
{
    readonly private int maxIterations;

    public Mandelbrot(int maxIterations)
    {
        this.maxIterations = maxIterations;
    }

    public int this [double x, double y]
    {
        get
        {
            var iterations = 0;
            var x0 = x;
            var y0 = y;

            while ((x*x + y * y < 4) &&
                (iterations < maxIterations))
            {
                var newX = x * x - y * y + x0;
                y = 2 * x * y + y0;
                x = newX;
                iterations++;
            }
            return iterations;
        }
    }
}

Die Mandelbrot-Menge definiert Werte an jeder (x, y)-Koordinaten für reelle Zahlenwerte.The Mandelbrot Set defines values at every (x,y) coordinate for real number values. Dadurch wird ein Wörterbuch definiert, das eine unendliche Anzahl von Werten enthalten könnte.That defines a dictionary that could contain an infinite number of values. Deshalb liegt kein Speicher hinter der Menge.Therefore, there is no storage behind the set. Stattdessen berechnet diese Klasse den Wert für jeden Punkt, wenn die get-Zugriffsmethode von Code aufgerufen wird.Instead, this class computes the value for each point when code calls the get accessor. Es wird kein zugrundeliegender Speicher verwendet.There's no underlying storage used.

Hier ist ein letztes Beispiel für den Gebrauch von Indexern, in dem der Indexer mehrere Argumente mit unterschiedlichen Typen akzeptiert.Let's examine one last use of indexers, where the indexer takes multiple arguments of different types. Stellen Sie sich ein Programm vor, dass Daten zu historischen Temperaturen verwaltet.Consider a program that manages historical temperature data. Dieser Indexer verwendet eine Stadt und ein Datum, um die höchste und niedrigste Temperatur für diesen Ort festzulegen oder abzurufen:This indexer uses a city and a date to set or get the high and low temperatures for that location:

using DateMeasurements = 
    System.Collections.Generic.Dictionary<System.DateTime, IndexersSamples.Common.Measurements>;
using CityDataMeasurements = 
    System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<System.DateTime, IndexersSamples.Common.Measurements>>;

public class HistoricalWeatherData
{
    readonly CityDataMeasurements storage = new CityDataMeasurements();

    public Measurements this[string city, DateTime date]
    {
        get
        {
            var cityData = default(DateMeasurements);

            if (!storage.TryGetValue(city, out cityData))
                throw new ArgumentOutOfRangeException(nameof(city), "City not found");

            // strip out any time portion:
            var index = date.Date;
            var measure = default(Measurements);
            if (cityData.TryGetValue(index, out measure))
                return measure;
            throw new ArgumentOutOfRangeException(nameof(date), "Date not found");
        }
        set
        {
            var cityData = default(DateMeasurements);

            if (!storage.TryGetValue(city, out cityData))
            {
                cityData = new DateMeasurements();
                storage.Add(city, cityData);
            }

            // Strip out any time portion:
            var index = date.Date;
            cityData[index] = value;
        }
    }
}

In diesem Beispiel wird ein Indexer erstellt, der Wetterdaten zwei Argumenten zuordnet: eine Stadt (repräsentiert durch string) und ein Datum (repräsentiert durch DateTime).This example creates an indexer that maps weather data on two different arguments: a city (represented by a string) and a date (represented by a DateTime). Der interne Speicher verwendet zwei Dictionary-Klassen, die das zweidimensionale Wörterbuch repräsentieren.The internal storage uses two Dictionary classes to represent the two-dimensional dictionary. Die öffentliche API repräsentiert nicht mehr den zugrundeliegenden Speicher.The public API no longer represents the underlying storage. Stattdessen können Sie mit den Sprachfunktionen der Indexer eine öffentliche Schnittstelle erstellen, die Ihre Abstraktion repräsentiert, auch wenn der zugrundeliegende Speicher unterschiedliche Kernauflistungstypen verwenden muss.Rather, the language features of indexers enables you to create a public interface that represents your abstraction, even though the underlying storage must use different core collection types.

Es gibt zwei Teile dieses Codes, die Entwicklern möglicherweise unbekannt sind.There are two parts of this code that may be unfamiliar to some developers. Diese zwei using-Anweisungen:These two using statements:

using DateMeasurements = System.Collections.Generic.Dictionary<System.DateTime, IndexersSamples.Common.Measurements>;
using CityDataMeasurements = System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<System.DateTime, IndexersSamples.Common.Measurements>>;

erstellen einen Alias für einen konstruierter generischen Typen.create an alias for a constructed generic type. Durch diese Anweisungen kann ein Code später die deskriptiveren Namen DateMeasurements und CityDateMeasurements statt der generischen Konstruktion von Dictionary<DateTime, Measurements> und Dictionary<string, Dictionary<DateTime, Measurements> > verwenden.Those statements enable the code later to use the more descriptive DateMeasurements and CityDateMeasurements names instead of the generic construction of Dictionary<DateTime, Measurements> and Dictionary<string, Dictionary<DateTime, Measurements> >. Diese Konstruktion erfordert den Gebrauch des vollqualifizierten Typnamens auf der rechten Seite des =-Zeichens.This construct does require using the fully qualified type names on the right side of the = sign.

In der zweiten Vorgehensweise entfernen Sie die Uhrzeitteile jedes DateTime-Objekts, das verwendet wird, um in die Auflistungen zu indizieren.The second technique is to strip off the time portions of any DateTime object used to index into the collections. Das .NET Framework enthält keinen „Date only“-Typ.The .NET framework does not include a Date only type. Entwickler verwenden den DateTime-Typ, aber die Date-Eigenschaft, um sicherzustellen, das jedes DateTime-Objekt dieses Tages gleich ist.Developers use the DateTime type, but use the Date property to ensure that any DateTime object from that day are equal.

SchlussbemerkungSumming Up

Sie sollten Indexer immer dann erstellen, wenn Sie ein Element, das einer Eigenschaft ähnelt, in Ihrer Klasse vorliegen haben, und wenn diese Eigenschaft nicht einen einzelnen Wert repräsentiert, sondern eine Auflistung von Werten, in der jedes einzelne Element durch einen Satz von Argumenten identifiziert wird.You should create indexers anytime you have a property-like element in your class where that property represents not a single value, but rather a collection of values where each individual item is identified by a set of arguments. Diese Argumente können eindeutig identifizieren, auf welches Element in der Auflistung verwiesen werden sollte.Those arguments can uniquely identify which item in the collection should be referenced. Indexer erweitern das Konzept der Eigenschaften, wo ein Member außerhalb der Klasse nicht wie ein Datenelement behandelt wird, sondern wie eine Methode von innen.Indexers extend the concept of properties, where a member is treated like a data item from outside the class, but like a method on the side. Indexer ermöglichen es Argumenten, ein einzelnes Element in einer Eigenschaft zu finden, das einen Satz von Elementen repräsentiert.Indexers allow arguments to find a single item in a property that represents a set of items.