Sdílet prostřednictvím


Indexátory

Indexery definujete, když instance třídy nebo struktury mohou být indexovány jako pole nebo jiná kolekce. Indexovanou hodnotu lze nastavit nebo načíst bez explicitního zadání typu nebo člena instance. Indexery se podobají vlastnostem s tím rozdílem, že jejich přístupové objekty přebírají parametry.

Následující příklad definuje obecnou třídu s get a set přístupovými metodami pro načtení a přiřazení hodnot.

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

Předchozí příklad ukazuje indexer pro čtení a zápis. Obsahuje jak přístupové objekty get, tak set. Indexery pouze pro čtení můžete definovat jako člena tvořeného výrazem, jak je znázorněno v následujících příkladech.

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

}

Klíčové get slovo se nepoužívá. => Představuje tělo výrazu.

Indexery umožňují indexované vlastnosti: vlastnosti odkazované pomocí jednoho nebo více argumentů. Tyto argumenty poskytují index do některé kolekce hodnot.

  • Indexery umožňují indexování objektů podobných polím.
  • Přístupový get objekt vrací hodnotu. Přístupový set prvek přiřazuje hodnotu.
  • Klíčové this slovo definuje indexer.
  • Klíčové value slovo je argument pro set přístup.
  • Indexery nevyžadují celočíselnou hodnotu indexu; je na vás, jak definovat konkrétní mechanismus vyhledávání.
  • Indexery je možné přetížit.
  • Indexery můžou mít jeden nebo více formálních parametrů, například při přístupu k dvojrozměrné matici.
  • Indexery můžete deklarovatpartial v partial typech.

Můžete použít téměř všechno, co jste se naučili od práce s vlastnostmi na indexery. Jedinou výjimkou tohoto pravidla jsou automaticky implementované vlastnosti. Kompilátor nemůže vždy vygenerovat správné úložiště indexeru. Pro typ můžete definovat více indexerů, pokud jsou seznamy argumentů pro každý indexer jedinečné.

Použití indexerů

indexery definujete ve svém typu, když rozhraní jeho API modeluje nějakou kolekci. Indexátor nemusí být namapován přímo na typy kolekcí, které jsou součástí rozhraní .NET Core. Indexery umožňují poskytnout rozhraní API, které odpovídá abstrakci vašeho typu, aniž byste museli odhalit vnitřní podrobnosti o tom, jak se hodnoty pro tuto abstrakci ukládají nebo počítají.

Pole a vektory

Váš typ může modelovat pole nebo vektor. Výhodou vytvoření vlastního indexeru je, že můžete definovat úložiště pro danou kolekci tak, aby vyhovovalo vašim potřebám. Představte si scénář, ve kterém váš typ modeluje historická data, která nelze najednou načíst do paměti. Je potřeba načíst a vyložit části kolekce podle jejich využití. Následující příklad toto chování modeluje. Zaznamenává, kolik datových bodů existuje. Vytváří stránky k uchování částí dat na vyžádání. Odebere stránky z paměti, aby se uvolnilo místo pro stránky potřebné novějšími požadavky.

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

Podle tohoto návrhového idiomu můžete modelovat jakýkoli druh kolekce, kde existují dobré důvody, proč nenačíst celou sadu dat do kolekce v paměti. Všimněte si, že Page třída je soukromá vnořená třída, která není součástí veřejného rozhraní. Tyto podrobnosti jsou uživatelům této třídy skryté.

Slovníky

Dalším běžným scénářem je, když potřebujete modelovat slovník nebo mapu. Tento scénář nastává, když váš typ ukládá hodnoty na základě klíče, například textové klíče. Tento příklad vytvoří slovník, který mapuje argumenty příkazového řádku na výrazy lambda, které tyto možnosti ovládají. Následující příklad ukazuje dvě třídy: třída ArgsActions, která mapuje možnost příkazového řádku System.Action na delegáta, a třída ArgsProcessor, která používá ArgsActions k vykonání každé Action při narazení na danou možnost.

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

V tomto příkladu kolekce ArgsAction úzce odpovídá podkladové kolekci. Určuje get , jestli je daná možnost nakonfigurovaná. Pokud ano, vrátí Action přidruženou k této možnosti. Pokud ne, vrátí Action, který nic nedělá. Veřejný přístup neobsahuje přístupový objekt set . Místo toho návrh používá veřejnou metodu pro nastavení možností.

Multidimenzionální mapy

Můžete vytvořit indexery, které používají více argumentů. Kromě toho tyto argumenty nejsou omezené na stejný typ.

Následující příklad ukazuje třídu, která generuje hodnoty pro Mandelbrotovu množinu. Další informace o matematice za sadou najdete v tomto článku. Indexer pomocí dvou dvojitých hodnot definuje bod v rovině X, Y. Přístupová get funkce vypočítá počet iterací, než je bod určen jako vnější vůči množině. Při dosažení maximálního počtu iterací se bod nachází v sadě a vrátí se hodnota maxIterations třídy. (Počítač generované obrázky popularizované pro sadu Mandelbrot definují barvy pro počet iterací nezbytných k určení, že bod je mimo sadu.)

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

Sada Mandelbrot definuje hodnoty v každé souřadnici (x,y) pro reálné číselné hodnoty. Definuje slovník, který by mohl obsahovat nekonečný počet hodnot. ** Za sadou tedy není žádné úložiště. Místo toho tato třída vypočítá hodnotu pro každý bod, když kód volá get přístupový objekt. Nepoužívá se žádné podkladové úložiště.

Závěrečná řeč soudce

Indexery vytváříte vždy, když máte ve třídě element podobný vlastnosti, kde tato vlastnost nepředstavuje jednu hodnotu, ale spíše sadu hodnot. Jeden nebo více argumentů identifikuje jednotlivé položky. Tyto argumenty mohou jednoznačně identifikovat, na kterou položku v sadě se má odkazovat. Indexery rozšiřují koncept vlastností, kde je člen navenek považován za datovou položku, ale uvnitř za metodu. Indexery umožňují argumentům najít jednu položku ve vlastnosti, která představuje sadu položek.

Můžete získat přístup k ukázkové složce pro indexery. Pokyny ke stažení najdete ve vzorech a návodech .

Specifikace jazyka C#

Další informace naleznete v tématu Indexery ve specifikaci jazyka C#. Specifikace jazyka je konečným zdrojem syntaxe a použití jazyka C#.