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


Indexelők

Az indexelők hasonlóak a tulajdonságokhoz. Az indexelők sok szempontból ugyanazokra a nyelvi funkciókra épülnek, mint a tulajdonságok. 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.

Indexelő szintaxisa

Egy indexelőt változónévvel és szögletes zárójelekkel érhet el. Az indexelő argumentumokat a zárójelek közé kell helyeznie:

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

A kulcsszót használó this indexelőket deklarálja tulajdonságnévként, és szögletes zárójelben deklarálja az argumentumokat. Ez a deklaráció megegyezik az előző bekezdésben látható használattal:

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

Ebből a kezdeti példából láthatja a tulajdonságok szintaxisa és az indexelők közötti kapcsolatot. Ez az analógia végigvezeti az indexelők szintaxisi szabályainak többségén. Az indexelők bármilyen érvényes hozzáférés-módosítóval rendelkezhetnek (nyilvános, védett belső, védett, belső, magán- vagy magánvédett). Ezek lehetnek lezártak, virtuálisak vagy absztraktak. A tulajdonságokhoz hasonlóan különböző hozzáférési módosítókat is megadhat az indexelők beolvasásához és beállításához. Írásvédett indexelőket (a készlet tartozék kihagyásával) vagy írásvédett indexelőket is megadhat (a beolvasási tartozék kihagyásával).

Szinte mindent alkalmazhat, amit a tulajdonságok használatával tanult az indexelőkre. A szabály egyetlen kivétele az automatikusan implementált tulajdonságok. A fordító nem mindig tudja létrehozni az indexelők számára a megfelelő tárterületet.

Az elemekre hivatkozó argumentumok jelenléte egy elemcsoportban megkülönbözteti az indexelőket a tulajdonságoktól. Egy típushoz több indexelőt is definiálhat, feltéve, hogy az egyes indexelők argumentumlistái egyediek. Vizsgáljuk meg a különböző forgatókönyveket, ahol egy vagy több indexelőt használhat egy osztálydefinícióban.

Forgatókönyvek

Az indexelőket akkor definiálná a típusában, amikor az API modellel egy gyűjteményt, ahol ön határozza meg az adott gyűjtemény argumentumait. Előfordulhat, hogy az indexelők közvetlenül a .NET Core-keretrendszer részét képező gyűjteménytípusokra vannak leképezve. A gyűjtemény modellezése mellett a típusnak más feladatai is lehetnek. 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.

Tekintsük át az indexelők használatának néhány gyakori forgatókönyvét. Az indexelők mintamappája elérhető. A letöltési utasításokért tekintse meg a példákat és az oktatóanyagokat.

Tömbök és vektorok

Az indexelők létrehozásának egyik leggyakoribb forgatókönyve, ha a típus tömböt vagy vektort modellez. Létrehozhat egy indexelőt az adatok rendezett listájának modellezéséhez.

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. Azt 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.

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

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 rejtve vannak az osztály bármely felhasználója elől.

Szótárak

Egy másik gyakori forgatókönyv az, amikor egy szótárt vagy egy térképet kell modellezhet. Ez a forgatókönyv az, amikor a típus kulcs, általában szöveges kulcsok 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 Action delegálthoz, és egy ArgsProcessor olyan osztályt, amely mindegyik végrehajtásához Action használja, amikor találkozik ezzel a ArgsActions beállítással.

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

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 egy semmit nem tevő értéket ad Action vissza. A nyilvános tartozék nem tartalmaz tartozékot set . 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 nem lesznek azonos típusúak. Tekintsünk meg két példát.

Az első példa egy olyan osztályt mutat be, amely értékeket hoz létre egy Mandelbrot-készlethez. A készlet mögötti matematikával kapcsolatos további információkért olvassa el ezt a cikket. Az indexelő két dupla használatával határoz meg egy pontot az X, Y síkban. A get tartozék kiszámítja az iterációk számát, amíg egy pont nem lesz a készletben. Ha eléri a maximális iterációkat, a pont a halmazban van, és az osztály maxIterations értéke lesz visszaadva. (A számítógép által a Mandelbrot-készlethez népszerűvé vált képek határozzák meg az iterációk számának színeit, amelyek annak megállapításához szükségesek, hogy egy pont kívül esik a készleten.)

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

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 készlet 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ó.

Vizsgáljuk meg az indexelők egy utolsó használatát, ahol az indexelő több különböző típusú argumentumot használ. Vegyünk egy olyan programot, amely az előzményhőmérséklet adatait kezeli. Ez az indexelő egy várost és egy dátumot használ az adott hely magas és alacsony hőmérsékletének beállításához vagy lekéréséhez:

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

Ez a példa létrehoz egy indexelőt, amely az időjárási adatokat két különböző argumentum alapján jeleníti meg: egy várost (a string) és egy dátumot (amelyet egy DateTime). A belső tároló két Dictionary osztályt használ a kétdimenziós szótár megjelenítéséhez. A nyilvános API már nem a mögöttes tárolót jelöli. Az indexelők nyelvi funkciói lehetővé teszik az absztrakciót jelképező nyilvános felület létrehozását, annak ellenére, hogy a mögöttes tárolónak különböző alapvető gyűjteménytípusokat kell használnia.

A kódnak két olyan része van, amelyek egyes fejlesztők számára ismeretlenek lehetnek. Ez a két using irányelv:

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

létrehoz egy aliast egy létrehozott általános típushoz. Ezek az utasítások lehetővé teszik, hogy a kód később a leíróbb DateMeasurements és CityDataMeasurements a neveket használja ahelyett, hogy az általános felépítését Dictionary<DateTime, Measurements> és Dictionary<string, Dictionary<DateTime, Measurements> >. Ehhez a szerkezethez a jel jobb oldalán = található teljes típusneveket kell használni.

A második technika a gyűjteményekbe való indexeléshez használt objektumok időrészeinek DateTime leválasztása. A .NET nem tartalmaz csak dátum típusú típust. A fejlesztők ezt a típust DateTime használják, de a Date tulajdonság használatával biztosítják, hogy az adott napból származó objektumok DateTime egyenlőek legyenek.

Összegzés felfelé

Minden olyan esetben érdemes indexelőket létrehoznia, amikor az osztály egy tulajdonsághoz hasonló elemével rendelkezik, ahol ez a tulajdonság nem egyetlen értéket jelöl, hanem olyan értékgyűjteményt, amelyben az egyes elemeket argumentumok halmaza azonosítja. Ezek az argumentumok egyedileg azonosíthatják, hogy a gyűjtemény 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.