Megosztás a következőn keresztül:


Indexelők

Indexelőket akkor definiálhat, ha egy osztály vagy szerkezet példányai tömbként vagy más gyűjteményként indexelhetők. Az indexelt érték típus vagy példánytag explicit megadása nélkül állítható be vagy kérhető le. Az indexelők a tulajdonságokra hasonlítanak, azzal a kivételével, hogy a tartozékaik paramétereket vesznek fel.

Az alábbi példa egy általános osztályt határoz meg az értékek hozzárendelésére és get lekérésére szolgáló kiegészítő metódusokkalset.

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

Az előző példában egy olvasási/írási indexelő látható. Tartalmazza mind a get, mind a set hozzáférési eszközöket. Csak olvasható indexelőket definiálhat kifejezés alapú tagként, ahogy az a következő példákban látható:

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

}

A get kulcsszó nincs használatban; => bemutatja a kifejezés törzsét.

Az indexelők engedélyezik az indexelt tulajdonságokat: egy vagy több argumentum használatával hivatkozott tulajdonságok. Ezek az argumentumok indexet biztosítanak néhány értékgyűjteményhez.

  • Az indexelők lehetővé teszik, hogy az objektumok a tömbökhöz hasonlóan indexelhetők legyenek.
  • A get tartozék egy értéket ad vissza. A set tartozék értékeket rendel hozzá.
  • A this kulcsszó határozza meg az indexelőt.
  • A value kulcsszó a set hozzáférő argumentuma.
  • Az indexelőknek nincs szükségük egész indexértékre; Önön múlik, hogyan határozhatja meg az adott keresési mechanizmust.
  • Az indexelők túlterhelhetők.
  • Az indexelők egy vagy több formális paraméterrel is rendelkezhetnek, például kétdimenziós tömb elérésekor.
  • Az indexelőket típusokbanpartial deklarálhatjapartial.

Szinte mindent alkalmazhat, amit a tulajdonságok használata során tanult az indexelőkre. A szabály egyetlen kivétele a tulajdonságok automatikus implementálása. A fordító nem mindig tudja létrehozni az indexelők számára a megfelelő tárterületet. Egy típuson több indexelőt is definiálhat, ha az egyes indexelők argumentumlistái egyediek.

Indexelők használata

Az indexelőket a saját típusában határozza meg, amikor az API valamilyen gyűjteményt modellez. Az indexelőnek nem szükséges közvetlenül leképeznie a .NET Core keretrendszer részét képező gyűjteménytípusokat. Az indexelők lehetővé teszik, hogy a típus absztrakciójának megfelelő API-t adja meg anélkül, hogy felfedi az absztrakció értékeinek tárolási vagy számítási módját.

Tömbök és vektorok

A típus tömböt vagy vektort modellezhet. A saját indexelő létrehozásának előnye, hogy az igényeinek megfelelően meghatározhatja a gyűjtemény tárterületét. Képzeljen el egy forgatókönyvet, amelyben a típus olyan előzményadatokat modell, amelyek túl nagyok ahhoz, hogy egyszerre betöltse a memóriába. A használat alapján be kell töltenie és ki kell ürítenie a gyűjtemény szakaszait. Az alábbi példa ezt a viselkedést modellozza. Jelenti, hogy hány adatpont létezik. Oldalakat hoz létre az adatok szakaszainak igény szerinti tárolásához. Eltávolítja az oldalakat a memóriából, hogy helyet biztosítson a legutóbbi kérések által igényelt lapoknak.

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

Ezt a tervezési idiómát követve bármilyen gyűjteményt modellezhet, ahol jó oka van annak, hogy a teljes adatkészletet ne töltse be egy memórián belüli gyűjteménybe. Figyelje meg, hogy az Page osztály egy privát beágyazott osztály, amely nem része a nyilvános felületnek. Ezek a részletek el vannak rejtve az osztály felhasználói elől.

Szótárak

Egy másik gyakori forgatókönyv az, amikor egy szótárt vagy egy térképet kell modellezni. Ez a forgatókönyv az, amikor a típus kulcs, esetleg szövegkulcsok alapján tárolja az értékeket. Ez a példa létrehoz egy szótárat, amely parancssori argumentumokat képez le a beállításokat kezelő Lambda-kifejezésekhez . Az alábbi példa két osztályt mutat be: egy ArgsActions osztályt, amely leképez egy parancssori beállítást egy System.Action delegálthoz, és egy ArgsProcessor olyan osztályt, amely mindegyik végrehajtásához ArgsActions használja, amikor találkozik ezzel a Action beállítással.

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

Ebben a példában a ArgsAction gyűjtemény szorosan megfelel az alapul szolgáló gyűjteménynek. Ez get határozza meg, hogy egy adott beállítás konfigurálva van-e. Ha igen, akkor az Action adott beállításhoz társított értéket adja vissza. Ha nem, akkor Action egy semmit nem csináló értéket ad vissza. A nyilvános elérési metódus nem tartalmaz set elérési metódust. Ehelyett a kialakítás egy nyilvános módszert használ a beállítások megadásához.

Többdimenziós térképek

Több argumentumot használó indexelőket is létrehozhat. Ezen kívül ezek az argumentumok nincsenek kötve ahhoz, hogy azonos típusúak legyenek.

Az alábbi példa egy mandelbrotkészlet értékeit generáló osztályt mutat be. A készlet mögötti matematikával kapcsolatos további információkért olvassa el ezt a cikket. Az indexelő két double típus használatával határoz meg egy pontot az X, Y síkban. A get hozzáférő kiszámítja az iterációk számát, amíg meg nem állapítja, hogy egy pont nem része a készletnek. Az iterációk maximális számának elérésekor a pont a halmazban van, és az osztály maxIterations értéke lesz visszaadva. A Mandelbrot-halmazhoz kapcsolódó, számítógéppel generált képek határozzák meg az iterációk számához tartozó színeket, amelyek annak eldöntéséhez szükségesek, hogy egy pont a halmazon kívül helyezkedik el.

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

A Mandelbrot-készlet a valós számértékek minden (x,y) koordinátáján meghatároz értékeket. Ez meghatároz egy olyan szótárt, amely végtelen számú értéket tartalmazhat. Ezért nincs tároló a szett mögött. Ehelyett ez az osztály kiszámítja az egyes pontok értékét, amikor a kód meghívja a tartozékot get . Nincs mögöttes tároló.

Összegzés

Az indexelőket bármikor létrehozhatja, amikor tulajdonságszerű elem van az osztályban, ahol ez a tulajdonság nem egyetlen értéket, hanem értékhalmazt jelöl. Egy vagy több argumentum azonosítja az egyes elemeket. Ezek az argumentumok egyedileg azonosítják, hogy a halmaz melyik elemére kell hivatkozni. Az indexelők kibővítik a tulajdonságok fogalmát, ahol a rendszer egy tagot az osztályon kívülről származó adatelemként kezel, de belülről mint egy metódust. Az indexelők lehetővé teszik, hogy az argumentumok egyetlen elemet keressenek egy olyan tulajdonságban, amely egy elemkészletet jelöl.

A mintamappa indexelők számára elérhető. A letöltési utasításokért tekintse meg példákat és oktatóanyagokat.

C# nyelvi specifikáció

További információ: Indexelők a C# nyelvi specifikációjában. A nyelvi specifikáció a C#-szintaxis és -használat végleges forrása.