Indexeerfuncties

Indexeerfuncties zijn vergelijkbaar met eigenschappen. Op veel manieren bouwen indexeerfuncties op dezelfde taalfuncties als eigenschappen. Indexeerfuncties schakelen geïndexeerde eigenschappen in: eigenschappen waarnaar wordt verwezen met behulp van een of meer argumenten. Deze argumenten bieden een index in een verzameling waarden.

Syntaxis van indexeerfunctie

U opent een indexeerfunctie via een variabelenaam en vierkante haken. U plaatst de indexeerfunctieargumenten tussen de vierkante haken:

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

U declareert indexeerfuncties met behulp van het this trefwoord als de naam van de eigenschap en het declareren van de argumenten binnen vierkante haken. Deze declaratie komt overeen met het gebruik dat in de vorige alinea wordt weergegeven:

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

In dit eerste voorbeeld ziet u de relatie tussen de syntaxis voor eigenschappen en voor indexeerfuncties. Deze analogie doorloopt de meeste syntaxisregels voor indexeerfuncties. Indexeerfuncties kunnen geldige toegangsaanpassingen hebben (openbaar, beveiligd intern, beveiligd, intern, privé of privé beveiligd). Ze kunnen worden verzegeld, virtueel of abstract. Net als bij eigenschappen kunt u verschillende toegangsaanpassingen opgeven voor het ophalen en instellen van accessors in een indexeerfunctie. U kunt ook indexeerfuncties met het kenmerk Alleen-lezen opgeven (door de ingestelde toegangsfunctie weg te laten) of indexeerfuncties met alleen-schrijven (door de get-accessor weg te laten).

U kunt bijna alles wat u leert van het werken met eigenschappen toepassen op indexeerfuncties. De enige uitzondering op die regel is eigenschappen die automatisch zijn geïmplementeerd. De compiler kan niet altijd de juiste opslag genereren voor een indexeerfunctie.

De aanwezigheid van argumenten om naar een item in een set items te verwijzen, onderscheidt indexeerfuncties van eigenschappen. U kunt meerdere indexeerfuncties voor een type definiëren, zolang de argumentlijsten voor elke indexeerfunctie uniek zijn. Laten we eens kijken naar verschillende scenario's waarin u een of meer indexeerfuncties in een klassedefinitie kunt gebruiken.

Scenario's

U definieert indexeerfuncties in uw type wanneer de API-modellen een verzameling modelleren waarin u de argumenten voor die verzameling definieert. Uw indexeerfuncties kunnen al dan niet rechtstreeks worden toegewezen aan de verzamelingstypen die deel uitmaken van het .NET Core-framework. Uw type kan naast het modelleren van een verzameling ook andere verantwoordelijkheden hebben. Met indexeerfuncties kunt u de API opgeven die overeenkomt met de abstractie van uw type zonder de binnenste details weer te geven van hoe de waarden voor die abstractie worden opgeslagen of berekend.

Laten we enkele veelvoorkomende scenario's voor het gebruik van indexeerfuncties bekijken. U hebt toegang tot de voorbeeldmap voor indexeerfuncties. Zie Voorbeelden en zelfstudies voor downloadinstructies.

Matrices en vectoren

Een van de meest voorkomende scenario's voor het maken van indexeerfuncties is wanneer uw type een matrix of een vector modelleert. U kunt een indexeerfunctie maken om een geordende lijst met gegevens te modelleren.

Het voordeel van het maken van uw eigen indexeerfunctie is dat u de opslag voor die verzameling kunt definiëren op basis van uw behoeften. Stel u een scenario voor waarbij uw type historische gegevens modelleert die te groot zijn om in het geheugen tegelijk te laden. U moet secties van de verzameling laden en verwijderen op basis van gebruik. In het volgende voorbeeld ziet u dit gedrag. Er wordt gerapporteerd hoeveel gegevenspunten er bestaan. Hiermee worden pagina's gemaakt voor het opslaan van secties van de gegevens op aanvraag. Hiermee worden pagina's uit het geheugen verwijderd om ruimte te maken voor pagina's die nodig zijn voor recentere aanvragen.

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

U kunt deze ontwerpidioom volgen om een soort verzameling te modelleren waarbij er goede redenen zijn om de volledige set gegevens niet in een in-memory verzameling te laden. U ziet dat de Page klasse een persoonlijke geneste klasse is die geen deel uitmaakt van de openbare interface. Deze gegevens zijn verborgen voor alle gebruikers van deze klasse.

Woordenlijsten

Een ander veelvoorkomend scenario is wanneer u een woordenlijst of kaart moet modelleren. In dit scenario worden waarden opgeslagen op basis van sleutel, meestal teksttoetsen. In dit voorbeeld wordt een woordenlijst gemaakt waarmee opdrachtregelargumenten worden toegewezen aan lambda-expressies waarmee deze opties worden beheerd. In het volgende voorbeeld ziet u twee klassen: een ArgsActions klasse die een opdrachtregeloptie toe wijst aan een Action gemachtigde en een ArgsProcessor klasse die de ArgsActions opdracht gebruikt om elke Action klasse uit te voeren wanneer deze optie tegenkomt.

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 dit voorbeeld wordt de ArgsAction verzameling nauw toegewezen aan de onderliggende verzameling. Hiermee get wordt bepaald of een bepaalde optie is geconfigureerd. Zo ja, dan wordt de Action gekoppelde optie geretourneerd. Als dat niet het geval is, wordt er een Action geretourneerd die niets doet. De openbare accessor bevat set geen toegangsbeheerprogramma. In plaats daarvan gebruikt het ontwerp een openbare methode voor het instellen van opties.

Multidimensionale Kaarten

U kunt indexeerfuncties maken die meerdere argumenten gebruiken. Bovendien zijn deze argumenten niet beperkt tot hetzelfde type. Laten we eens kijken naar twee voorbeelden.

In het eerste voorbeeld ziet u een klasse waarmee waarden voor een Mandelbrot-set worden gegenereerd. Lees dit artikel voor meer informatie over de wiskunde achter de set. De indexeerfunctie gebruikt twee dubbele punten om een punt in het X-, Y-vlak te definiëren. De get-accessor berekent het aantal iteraties totdat wordt vastgesteld dat een punt zich niet in de set bevindt. Als de maximale iteraties zijn bereikt, bevindt het punt zich in de set en wordt de waarde maxIterations van de klasse geretourneerd. (De computer gegenereerde afbeeldingen populair voor de Mandelbrot-set definiëren kleuren voor het aantal iteraties dat nodig is om te bepalen dat een punt zich buiten de set bevindt.)

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

De Mandelbrot-set definieert waarden bij elke (x,y) coördinaat voor reële getalwaarden. Hiermee definieert u een woordenlijst die een oneindig aantal waarden kan bevatten. Daarom is er geen opslag achter de set. In plaats daarvan berekent deze klasse de waarde voor elk punt wanneer code de get accessor aanroept. Er is geen onderliggende opslag gebruikt.

Laten we eens kijken naar een laatste gebruik van indexeerfuncties, waarbij de indexeerfunctie meerdere argumenten van verschillende typen gebruikt. Overweeg een programma waarmee historische temperatuurgegevens worden beheerd. Deze indexeerfunctie gebruikt een plaats en een datum om de hoge en lage temperaturen voor die locatie in te stellen of op te halen:

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 dit voorbeeld wordt een indexeerfunctie gemaakt waarmee weergegevens op twee verschillende argumenten worden toegewezen: een stad (vertegenwoordigd door een string) en een datum (vertegenwoordigd door een DateTime). De interne opslag maakt gebruik van twee Dictionary klassen om de tweedimensionale woordenlijst weer te geven. De openbare API vertegenwoordigt niet langer de onderliggende opslag. In plaats daarvan kunt u met de taalfuncties van indexeerfuncties een openbare interface maken die uw abstractie vertegenwoordigt, ook al moet de onderliggende opslag verschillende typen kernverzamelingen gebruiken.

Er zijn twee delen van deze code die mogelijk onbekend zijn voor sommige ontwikkelaars. Deze twee using richtlijnen:

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

maak een alias voor een samengesteld algemeen type. Met deze instructies kan de code later de meer beschrijvende DateMeasurements en CityDataMeasurements namen gebruiken in plaats van de algemene constructie van Dictionary<DateTime, Measurements> en Dictionary<string, Dictionary<DateTime, Measurements> >. Voor deze constructie is het gebruik van de volledig gekwalificeerde typenamen aan de rechterkant van het = teken vereist.

De tweede techniek is het verwijderen van de tijdgedeelten van elk DateTime object dat wordt gebruikt om in de verzamelingen te indexeren. .NET bevat geen datumtype. Ontwikkelaars gebruiken het DateTime type, maar gebruiken de Date eigenschap om ervoor te zorgen dat elk DateTime object van die dag gelijk is.

Samenvatting

U moet indexeerfuncties maken wanneer u een eigenschapsachtig element in uw klasse hebt waarin die eigenschap niet één waarde vertegenwoordigt, maar een verzameling waarden waarbij elk afzonderlijk item wordt geïdentificeerd door een set argumenten. Deze argumenten kunnen op unieke wijze bepalen naar welk item in de verzameling moet worden verwezen. Indexeerfuncties breiden het concept van eigenschappen uit, waarbij een lid wordt behandeld als een gegevensitem van buiten de klasse, maar als een methode aan de binnenkant. Met indexeerfuncties kunnen argumenten één item vinden in een eigenschap die een set items vertegenwoordigt.