Delen via


Indexeerders

U definieert indexeerfuncties wanneer exemplaren van een klasse of struct kunnen worden geïndexeerd, zoals een matrix of andere verzameling. De geïndexeerde waarde kan worden ingesteld of verkregen zonder expliciet een type of instantie-lid op te geven. Indexeerfuncties lijken op eigenschappen , behalve dat hun accessors parameters aannemen.

In het volgende voorbeeld wordt een algemene klasse met get en set toegangsmethoden gedefinieerd om waarden toe te wijzen en op te halen.

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

In het voorgaande voorbeeld ziet u een indexeerfunctie voor lezen/schrijven. Het bevat zowel de get- als de set-accessors. U kunt indexeerfuncties met het kenmerk Alleen-lezen definiëren als een expressie, zoals wordt weergegeven in de volgende voorbeelden:

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

}

Het get trefwoord wordt niet gebruikt; => introduceert de hoofdtekst van de expressie.

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.

  • Met indexeerfuncties kunnen objecten worden geïndexeerd die vergelijkbaar zijn met matrices.
  • Een get accessor retourneert een waarde. Een set accessor wijst een waarde toe.
  • Het this trefwoord definieert de indexeerfunctie.
  • Het value trefwoord is het argument voor de set accessor.
  • Indexeerfuncties vereisen geen geheel getal-indexwaarde; Het is aan u om het specifieke opzoekmechanisme te definiëren.
  • Indexeerfuncties kunnen overbelast worden.
  • Indexeerfuncties kunnen een of meer formele parameters hebben, bijvoorbeeld bij het openen van een tweedimensionale matrix.
  • U kunt indexeerfuncties declarerenpartial in partial typen.

U kunt bijna alles toepassen wat u hebt geleerd van het werken met eigenschappen voor indexeerfuncties. De enige uitzondering op die regel is automatisch geïmplementeerde eigenschappen. De compiler kan niet altijd de juiste opslag genereren voor een indexeerfunctie. U kunt meerdere indexeerfuncties voor een type definiëren, zolang de argumentlijsten voor elke indexeerfunctie uniek zijn.

Gebruik van indexeerfuncties

U definieert indexers in uw type wanneer de API een verzameling modelleert. Uw indexeerfunctie is niet vereist om rechtstreeks toe te wijzen aan de verzamelingstypen die deel uitmaken van het .NET Core Framework. 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.

Matrices en vectoren

Uw type kan een matrix of een vector 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.

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

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 privé geneste klasse is die geen deel uitmaakt van de openbare interface. Deze gegevens zijn verborgen voor gebruikers van deze klasse.

Woordenboeken

Een ander veelvoorkomend scenario is wanneer u een woordenlijst of kaart moet modelleren. In dit scenario worden waarden opgeslagen op basis van sleutel, mogelijk tekstsleutels. 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 System.Action gemachtigde en een ArgsProcessor klasse die de ArgsActions opdracht gebruikt om elke Action klasse uit te voeren wanneer deze optie tegenkomt.

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 dit voorbeeld komt de ArgsAction verzameling nauw overeen met de onderliggende verzameling. De get bepaalt of een bepaalde optie is geconfigureerd. Zo ja, dan retourneert het de Action die bij die optie hoort. Als dat niet het geval is, wordt er een Action geretourneerd die niets doet. De openbare accessor bevat geen set accessor. 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.

In het volgende 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 indexeerder gebruikt twee doubles om een punt in het XY-vlak te definiëren. De get accessor berekent het aantal iteraties totdat wordt bepaald dat een punt zich niet in de set bevindt. Wanneer het maximum aantal iteraties is bereikt, bevindt het punt zich in de set en wordt de waarde voor 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.)

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

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.

Samenvatting

U maakt indexeerfuncties wanneer u een eigenschapsachtig element in uw klasse hebt, waarbij die eigenschap niet één waarde vertegenwoordigt, maar in plaats daarvan een set waarden. Een of meer argumenten identificeren elk afzonderlijk item. Deze argumenten kunnen op unieke wijze bepalen naar welk item in de set 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.

U hebt toegang tot de voorbeeldmap voor indexeerfuncties. Zie voorbeelden en zelfstudiesvoor downloadinstructies.

C#-taalspecificatie

Zie Indexeerfuncties in de C#-taalspecificatie voor meer informatie. De taalspecificatie is de definitieve bron voor de C#-syntaxis en het gebruik.