Indexen en bereiken

Bereiken en indexen bieden een beknopte syntaxis voor toegang tot enkele elementen of bereiken in een reeks.

In deze zelfstudie leert u het volgende:

  • Gebruik de syntaxis voor bereiken in een reeks.
  • Impliciet een Range.
  • Inzicht in de ontwerpbeslissingen voor het begin en einde van elke reeks.
  • Leer scenario's voor de Index en Range typen.

Taalondersteuning voor indexen en bereiken

Indexen en bereiken bieden een beknopte syntaxis voor toegang tot enkele elementen of bereiken in een reeks.

Deze taalondersteuning is afhankelijk van twee nieuwe typen en twee nieuwe operators:

Laten we beginnen met de regels voor indexen. Overweeg een matrix sequence. De 0 index is hetzelfde als sequence[0]. De ^0 index is hetzelfde als sequence[sequence.Length]. De expressie sequence[^0] genereert een uitzondering, net zoals dat het geval sequence[sequence.Length] is. Voor een willekeurig getal nis de index ^n hetzelfde als sequence.Length - n.

string[] words = [
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumps",    // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
];              // 9 (or words.Length) ^0

U kunt het laatste woord ophalen met de ^1 index. Voeg de volgende code toe onder de initialisatie:

Console.WriteLine($"The last word is {words[^1]}");

Een bereik geeft het begin en einde van een bereik aan. Het begin van het bereik is inclusief, maar het einde van het bereik is exclusief, wat betekent dat het begin is opgenomen in het bereik, maar het einde niet in het bereik is opgenomen. Het bereik [0..^0] vertegenwoordigt het hele bereik, net zoals [0..sequence.Length] het hele bereik.

Met de volgende code wordt een subbereik gemaakt met de woorden 'quick', 'brown' en 'fox'. Het omvat words[1] via words[3]. Het element words[4] bevindt zich niet in het bereik.

string[] quickBrownFox = words[1..4];
foreach (var word in quickBrownFox)
    Console.Write($"< {word} >");
Console.WriteLine();

De volgende code retourneert het bereik met 'luie' en 'hond'. Het omvat words[^2] en words[^1]. De eindindex words[^0] is niet opgenomen. Voeg ook de volgende code toe:

string[] lazyDog = words[^2..^0];
foreach (var word in lazyDog)
    Console.Write($"< {word} >");
Console.WriteLine();

In de volgende voorbeelden worden bereiken gemaakt die zijn geopend voor het begin, einde of beide:

string[] allWords = words[..]; // contains "The" through "dog".
string[] firstPhrase = words[..4]; // contains "The" through "fox"
string[] lastPhrase = words[6..]; // contains "the", "lazy" and "dog"
foreach (var word in allWords)
    Console.Write($"< {word} >");
Console.WriteLine();
foreach (var word in firstPhrase)
    Console.Write($"< {word} >");
Console.WriteLine();
foreach (var word in lastPhrase)
    Console.Write($"< {word} >");
Console.WriteLine();

U kunt ook bereiken of indexen als variabelen declareren. De variabele kan vervolgens worden gebruikt in de [ en ] tekens:

Index the = ^3;
Console.WriteLine(words[the]);
Range phrase = 1..4;
string[] text = words[phrase];
foreach (var word in text)
    Console.Write($"< {word} >");
Console.WriteLine();

In het volgende voorbeeld ziet u een groot aantal redenen voor deze keuzes. Wijzig x, yen z probeer verschillende combinaties uit. Wanneer u experimenteert, gebruikt u waarden die x kleiner zijn dan yen y kleiner zijn dan z voor geldige combinaties. Voeg de volgende code toe in een nieuwe methode. Probeer verschillende combinaties:

int[] numbers = [..Enumerable.Range(0, 100)];
int x = 12;
int y = 25;
int z = 36;

Console.WriteLine($"{numbers[^x]} is the same as {numbers[numbers.Length - x]}");
Console.WriteLine($"{numbers[x..y].Length} is the same as {y - x}");

Console.WriteLine("numbers[x..y] and numbers[y..z] are consecutive and disjoint:");
Span<int> x_y = numbers[x..y];
Span<int> y_z = numbers[y..z];
Console.WriteLine($"\tnumbers[x..y] is {x_y[0]} through {x_y[^1]}, numbers[y..z] is {y_z[0]} through {y_z[^1]}");

Console.WriteLine("numbers[x..^x] removes x elements at each end:");
Span<int> x_x = numbers[x..^x];
Console.WriteLine($"\tnumbers[x..^x] starts with {x_x[0]} and ends with {x_x[^1]}");

Console.WriteLine("numbers[..x] means numbers[0..x] and numbers[x..] means numbers[x..^0]");
Span<int> start_x = numbers[..x];
Span<int> zero_x = numbers[0..x];
Console.WriteLine($"\t{start_x[0]}..{start_x[^1]} is the same as {zero_x[0]}..{zero_x[^1]}");
Span<int> z_end = numbers[z..];
Span<int> z_zero = numbers[z..^0];
Console.WriteLine($"\t{z_end[0]}..{z_end[^1]} is the same as {z_zero[0]}..{z_zero[^1]}");

Niet alleen matrices ondersteunen indexen en bereiken. U kunt ook indexen en bereiken gebruiken met tekenreeks, Span<T>of ReadOnlySpan<T>.

Expressieconversies van impliciete bereikoperators

Wanneer u de syntaxis van de operatorexpressie van het bereik gebruikt, worden de begin- en eindwaarden impliciet geconverteerd naar een Index en vervolgens een nieuw Range exemplaar. In de volgende code ziet u een voorbeeld van een impliciete conversie van de syntaxis van de operator voor de bereikoperator en het bijbehorende expliciete alternatief:

Range implicitRange = 3..^5;

Range explicitRange = new(
    start: new Index(value: 3, fromEnd: false),
    end: new Index(value: 5, fromEnd: true));

if (implicitRange.Equals(explicitRange))
{
    Console.WriteLine(
        $"The implicit range '{implicitRange}' equals the explicit range '{explicitRange}'");
}
// Sample output:
//     The implicit range '3..^5' equals the explicit range '3..^5'

Belangrijk

Impliciete conversies van Int32 waaruit Index een ArgumentOutOfRangeException waarde wordt gegenereerd wanneer de waarde negatief is. Op dezelfde manier genereert de Index constructor een ArgumentOutOfRangeException wanneer de value parameter negatief is.

Typeondersteuning voor indexen en bereiken

Indexen en bereiken bieden duidelijke, beknopte syntaxis voor toegang tot één element of een reeks elementen in een reeks. Een indexexpressie retourneert meestal het type van de elementen van een reeks. Een bereikexpressie retourneert doorgaans hetzelfde reekstype als de bronreeks.

Elk type dat een indexeerfunctie met een Index of Range parameter biedt, ondersteunt respectievelijk indexen of bereiken. Een indexeerfunctie die één Range parameter gebruikt, kan een ander type reeks retourneren, zoals System.Span<T>.

Belangrijk

De prestaties van code met behulp van de bereikoperator zijn afhankelijk van het type reeksoperand.

De tijdcomplexiteit van de bereikoperator is afhankelijk van het type reeks. Als de reeks bijvoorbeeld een string of een matrix is, is het resultaat een kopie van de opgegeven sectie van de invoer, dus de tijdcomplexiteit is O(N) (waarbij N de lengte van het bereik is). Aan de andere kant, als het een System.Span<T> of een System.Memory<T>is, verwijst het resultaat naar hetzelfde backingarchief, wat betekent dat er geen kopie is en de bewerking O(1) is.

Naast de tijdcomplexiteit zorgt dit voor extra toewijzingen en kopieën, wat van invloed is op de prestaties. Overweeg in prestatiegevoelige code het gebruik Span<T> of Memory<T> als het reekstype, omdat de bereikoperator er niet voor toewijst.

Een type kan worden geteld als het een eigenschap heeft met de naam Length of Count met een toegankelijke getter en een retourtype int. Een telbaar type dat geen expliciete ondersteuning biedt voor indexen of bereiken, kan een impliciete ondersteuning bieden. Zie de secties impliciete indexondersteuning en impliciete bereikondersteuning van de notitie over het functievoorstel voor meer informatie. Bereiken die impliciete bereikondersteuning gebruiken, retourneren hetzelfde reekstype als de bronvolgorde.

De volgende .NET-typen ondersteunen bijvoorbeeld zowel indexen als bereiken: String, Span<T>en ReadOnlySpan<T>. De List<T> ondersteuning voor indexen, maar biedt geen ondersteuning voor bereiken.

Array heeft meer genuanceerd gedrag. Matrices met één dimensie ondersteunen zowel indexen als bereiken. Multidimensionale matrices bieden geen ondersteuning voor indexeerfuncties of bereiken. De indexeerfunctie voor een multidimensionale matrix heeft meerdere parameters, niet één parameter. Gelabelde matrices, ook wel een matrix van matrices genoemd, ondersteunen zowel bereiken als indexeerfuncties. In het volgende voorbeeld ziet u hoe u een rechthoekige subsectie van een gelabelde matrix kunt herhalen. Hiermee wordt de sectie in het midden herhaald, met uitzondering van de eerste en laatste drie rijen, en de eerste en laatste twee kolommen uit elke geselecteerde rij:

int[][] jagged = 
[
   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
   [10,11,12,13,14,15,16,17,18,19],
   [20,21,22,23,24,25,26,27,28,29],
   [30,31,32,33,34,35,36,37,38,39],
   [40,41,42,43,44,45,46,47,48,49],
   [50,51,52,53,54,55,56,57,58,59],
   [60,61,62,63,64,65,66,67,68,69],
   [70,71,72,73,74,75,76,77,78,79],
   [80,81,82,83,84,85,86,87,88,89],
   [90,91,92,93,94,95,96,97,98,99],
];

var selectedRows = jagged[3..^3];

foreach (var row in selectedRows)
{
    var selectedColumns = row[2..^2];
    foreach (var cell in selectedColumns)
    {
        Console.Write($"{cell}, ");
    }
    Console.WriteLine();
}

In alle gevallen wijst de bereikoperator voor Array het toewijzen van een matrix toe om de geretourneerde elementen op te slaan.

Scenario's voor indexen en bereiken

U gebruikt vaak bereiken en indexen als u een deel van een grotere reeks wilt analyseren. De nieuwe syntaxis is duidelijker bij het lezen van het gedeelte van de reeks. De lokale functie MovingAverage gebruikt een Range als argument. De methode inventariseert vervolgens alleen dat bereik bij het berekenen van de min, max en het gemiddelde. Probeer de volgende code in uw project:

int[] sequence = Sequence(1000);

for(int start = 0; start < sequence.Length; start += 100)
{
    Range r = start..(start+10);
    var (min, max, average) = MovingAverage(sequence, r);
    Console.WriteLine($"From {r.Start} to {r.End}:    \tMin: {min},\tMax: {max},\tAverage: {average}");
}

for (int start = 0; start < sequence.Length; start += 100)
{
    Range r = ^(start + 10)..^start;
    var (min, max, average) = MovingAverage(sequence, r);
    Console.WriteLine($"From {r.Start} to {r.End}:  \tMin: {min},\tMax: {max},\tAverage: {average}");
}

(int min, int max, double average) MovingAverage(int[] subSequence, Range range) =>
    (
        subSequence[range].Min(),
        subSequence[range].Max(),
        subSequence[range].Average()
    );

int[] Sequence(int count) => [..Enumerable.Range(0, count).Select(x => (int)(Math.Sqrt(x) * 100))];

Een notitie over bereikindexen en matrices

Bij het nemen van een bereik van een matrix is het resultaat een matrix die wordt gekopieerd uit de oorspronkelijke matrix in plaats van ernaar te verwijzen. Als u waarden wijzigt in de resulterende matrix, worden de waarden in de initiële matrix niet gewijzigd.

Bijvoorbeeld:

var arrayOfFiveItems = new[] { 1, 2, 3, 4, 5 };

var firstThreeItems = arrayOfFiveItems[..3]; // contains 1,2,3
firstThreeItems[0] =  11; // now contains 11,2,3

Console.WriteLine(string.Join(",", firstThreeItems));
Console.WriteLine(string.Join(",", arrayOfFiveItems));

// output:
// 11,2,3
// 1,2,3,4,5

Zie ook