Freigeben über


Indexierer

Sie definieren Indexer , wenn Instanzen einer Klasse oder Struktur wie ein Array oder eine andere Auflistung indiziert werden können. Der indizierte Wert kann festgelegt oder abgerufen werden, ohne einen Typ oder ein Instanzmemm explizit anzugeben. Indexer ähneln Eigenschaften , mit der Ausnahme, dass ihre Accessoren Parameter verwenden.

Im folgenden Beispiel wird eine generische Klasse mit get und set Accessormethoden zum Zuweisen und Abrufen von Werten definiert.

namespace Indexers;

public class SampleCollection<T>
{
   // Declare an array to store the data elements.
   private T[] arr = new T[100];

   // Define the indexer to allow client code to use [] notation.
   public T this[int i]
   {
      get => arr[i];
      set => arr[i] = value;
   }
}

Das vorangehende Beispiel zeigt einen Lese-/Schreib-Indexer. Sie enthält sowohl die get- als auch die set-Accessoren. Sie können nur schreibgeschützte Indexer als ein Mitglied mit Ausdruck-Body definieren, wie in den folgenden Beispielen gezeigt:

namespace Indexers;

public class ReadOnlySampleCollection<T>(params IEnumerable<T> items)
{
   // Declare an array to store the data elements.
   private T[] arr = [.. items];

   public T this[int i] => arr[i];

}

Das get Schlüsselwort wird nicht verwendet; => führt den Textkörper des Ausdrucks ein.

Indexer aktivieren indizierte Eigenschaften: Eigenschaften, auf die mithilfe eines oder mehrerer Argumente verwiesen wird. Diese Argumente stellen einen Index in einer Sammlung von Werten bereit.

  • Indexer ermöglichen es Objekten, ähnlich wie Arrays indiziert zu werden.
  • Ein get Accessor gibt einen Wert zurück. Ein set Accessor weist einen Wert zu.
  • Das this Schlüsselwort definiert den Indexer.
  • Das value Schlüsselwort ist das Argument für den set Accessor.
  • Indexer benötigen keinen ganzzahligen Indexwert; Es liegt an Ihnen, wie Sie den spezifischen Nachschlagemechanismus definieren.
  • Indexer können überladen werden.
  • Indexer können beispielsweise einen oder mehrere formale Parameter haben, wenn sie auf ein zweidimensionales Array zugreifen.
  • Sie können Indexer in partial Typen deklarierenpartial.

Sie können fast alles anwenden, was Sie von der Arbeit mit Eigenschaften auf Indexer gelernt haben. Die einzige Ausnahme von dieser Regel ist automatisch implementierte Eigenschaften. Der Compiler kann nicht immer den richtigen Speicher für einen Indexer generieren. Sie können mehrere Indexer für einen Typ definieren, solange die Argumentlisten für jeden Indexer eindeutig sind.

Verwendung von Indexern

Sie definieren Indexer in Ihrem Typ, wenn ihre API-Modelle eine Sammlung aufweisen. Ihr Indexer muss nicht direkt den Collection-Typen zugeordnet werden, die Teil des .NET Core Frameworks sind. Mit Indexern können Sie die API bereitstellen, die der Abstraktion Ihres Typs entspricht, ohne die inneren Details zur Speicherung oder Berechnung der Werte für diese Abstraktion verfügbar zu machen.

Arrays und Vektoren

Ihr Typ kann ein Array oder einen Vektor modellieren. Der Vorteil der Erstellung Eines eigenen Indexers besteht darin, dass Sie den Speicher für diese Sammlung für Ihre Anforderungen definieren können. Stellen Sie sich ein Szenario vor, in dem Ihr Typ historische Daten modelliert, die zu groß sind, um in den Arbeitsspeicher geladen zu werden. Sie müssen Abschnitte der Sammlung abhängig von der Nutzung laden und entladen. Im folgenden Beispiel wird dieses Verhalten modelliert. Es meldet, wie viele Datenpunkte vorhanden sind. Es erstellt Seiten, die Abschnitte der Daten bei Bedarf enthalten. Es entfernt Seiten aus dem Arbeitsspeicher, um Platz für Seiten zu schaffen, die von neueren Anforderungen benötigt werden.

namespace Indexers;

public record Measurements(double HiTemp, double LoTemp, double AirPressure);

public class DataSamples
{
    private class Page
    {
        private readonly List<Measurements> pageData = new ();
        private readonly int _startingIndex;
        private readonly int _length;

        public Page(int startingIndex, int length)
        {
            _startingIndex = startingIndex;
            _length = length;

            // 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 { get; private set; } = false;
        public DateTime LastAccess { get; set; } = DateTime.Now;
    }

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

    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 Design-Idiom folgen, um jegliche Sammlung zu modellieren, bei der es gute Gründe gibt, die gesamte Datenmenge nicht in eine In-Memory-Sammlung zu laden. Beachten Sie, dass es sich bei der Page Klasse um eine private geschachtelte Klasse handelt, die nicht Teil der öffentlichen Schnittstelle ist. Diese Details sind für Benutzer dieser Klasse ausgeblendet.

Wörterbücher

Ein weiteres gängiges Szenario ist das Modellieren eines Wörterbuchs oder einer Karte. In diesem Szenario speichert der Typ Werte basierend auf Schlüsseln, möglicherweise Textschlüsseln. In diesem Beispiel wird ein Wörterbuch erstellt, das Befehlszeilenargumente Lambda-Ausdrücken zuordnet, die diese Optionen verwalten. Das folgende Beispiel zeigt zwei Klassen: eine ArgsActions Klasse, die eine Befehlszeilenoption einem System.Action Delegat zuordnet, und eine ArgsProcessor Klasse, die ArgsActions verwendet, um jede Action auszuführen, bei Auftreten dieser Option.

namespace Indexers;
public class ArgsProcessor
{
    private readonly ArgsActions _actions;

    public ArgsProcessor(ArgsActions actions)
    {
        _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();

    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 stimmt die ArgsAction Sammlung eng mit der zugrunde liegenden Sammlung überein. Dies get bestimmt, ob eine bestimmte Option konfiguriert ist. Wenn ja, wird die mit dieser Option verbundene Action zurückgegeben. Wenn nicht, wird ein Action zurückgegeben, das nichts tut. Der öffentliche Accessor enthält keinen set-Accessor. Stattdessen verwendet der Entwurf eine öffentliche Methode zum Festlegen von Optionen.

Mehrdimensionale Karten

Sie können Indexer erstellen, die mehrere Argumente verwenden. Darüber hinaus sind diese Argumente nicht auf denselben Typ beschränkt.

Das folgende Beispiel zeigt eine Klasse, die Werte für ein Mandelbrot-Set generiert. Weitere Informationen zur Mathematik hinter dem Satz finden Sie in diesem Artikel. Der Indexer verwendet zwei Doubles, um einen Punkt in der X-, Y-Ebene zu definieren. Der get Accessor berechnet die Anzahl der Iterationen, bis ein Punkt bestimmt ist, dass er nicht im Satz enthalten ist. Wenn die maximale Anzahl von Iterationen erreicht wird, befindet sich der Punkt im Satz, und der MaxIterations-Wert der Klasse wird zurückgegeben. (Vom Computer generierte Bilder, die für den Mandelbrot-Satz beliebt sind, definieren Farben für die Anzahl der Iterationen, die erforderlich sind, um zu bestimmen, dass sich ein Punkt außerhalb des Satzes befindet.)

namespace Indexers;
public class Mandelbrot(int 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))
            { 
                (x, y) = (x * x - y * y + x0, 2 * x * y + y0);
                iterations++;
            }
            return iterations;
        }
    }
}

Der Mandelbrot-Satz definiert Werte an jeder (x,y)-Koordinate für reale Zahlenwerte. Dadurch wird ein Wörterbuch definiert, das eine unendliche Anzahl von Werten enthalten kann. Daher gibt es keinen Lagerplatz hinter dem Gerät. Stattdessen berechnet diese Klasse den Wert für jeden Punkt, wenn Code den get Accessor aufruft. Es wird kein zugrunde liegender Speicher verwendet.

Zusammenfassung

Sie erstellen Indexer, wenn Sie über ein eigenschaftsähnliches Element in Ihrer Klasse verfügen, in dem diese Eigenschaft nicht einen einzelnen Wert, sondern einen Satz von Werten darstellt. Ein oder mehrere Argumente identifizieren jedes einzelne Element. Mit diesen Argumenten kann eindeutig identifiziert werden, auf welches Element in der Menge verwiesen werden soll. Indexer erweitern das Konzept von Eigenschaften, bei denen ein Element wie ein Datenelement außerhalb der Klasse behandelt wird, aber wie eine Methode im Inneren. Indexer ermöglichen es Argumenten, ein einzelnes Element in einer Eigenschaft zu finden, die eine Gruppe von Elementen darstellt.

Sie können auf den Beispielordner für Indexer zugreifen. Anweisungen zum Herunterladen finden Sie unter Beispiele und Lernprogramme.

C#-Sprachspezifikation

Weitere Informationen finden Sie unter Indexer in der C#-Sprachspezifikation. Die Sprachspezifikation ist die endgültige Quelle für C#-Syntax und -Verwendung.