Index och intervall

Intervall och index ger en kortfattad syntax för åtkomst till enskilda element eller intervall i en sekvens.

I den här självstudien får du lära dig att:

  • Använd syntaxen för intervall i en sekvens.
  • Definiera implicit en Range.
  • Förstå designbesluten för början och slutet av varje sekvens.
  • Lär dig scenarier för typerna Index och Range .

Språkstöd för index och intervall

Index och intervall ger en kortfattad syntax för åtkomst till enskilda element eller intervall i en sekvens.

Det här språkstödet förlitar sig på två nya typer och två nya operatorer:

  • System.Index representerar ett index i en sekvens.
  • Indexet från slutoperatorn ^, som anger att ett index är relativt slutet av en sekvens.
  • System.Range representerar ett underintervall för en sekvens.
  • Intervalloperatorn .., som anger början och slutet av ett intervall som operander.

Vi börjar med reglerna för index. Överväg en matris sequence. Indexet 0 är detsamma som sequence[0]. Indexet ^0 är detsamma som sequence[sequence.Length]. Uttrycket sequence[^0] utlöser ett undantag, precis som sequence[sequence.Length] det gör. För valfritt tal när indexet ^n samma som 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

Du kan hämta det sista ordet med indexet ^1 . Lägg till följande kod under initieringen:

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

Ett intervall anger början och slutet av ett intervall. Början av intervallet är inkluderande, men slutet av intervallet är exklusivt, vilket innebär att starten ingår i intervallet men slutet ingår inte i intervallet. Intervallet [0..^0] representerar hela intervallet, precis som [0..sequence.Length] representerar hela intervallet.

Följande kod skapar en underordning med orden "quick", "brown" och "fox". Den innehåller words[1] via words[3]. Elementet words[4] finns inte i intervallet.

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

Följande kod returnerar intervallet med "lazy" och "dog". Den innehåller words[^2] och words[^1]. Slutindexet words[^0] ingår inte. Lägg även till följande kod:

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

I följande exempel skapas intervall som är öppna för start, slut eller båda:

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

Du kan också deklarera intervall eller index som variabler. Variabeln kan sedan användas inuti [ tecknen och ] :

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

Följande exempel visar många av orsakerna till dessa val. Ändra x, yoch z för att prova olika kombinationer. När du experimenterar använder du värden där x är mindre än y, och y är mindre än z för giltiga kombinationer. Lägg till följande kod i en ny metod. Prova olika kombinationer:

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

Matriser stöder inte bara index och intervall. Du kan också använda index och intervall med strängen , Span<T>eller ReadOnlySpan<T>.

Konverteringar av implicita intervalloperatoruttryck

När du använder syntaxen för intervalloperatorns uttryck konverterar kompilatorn implicit start- och slutvärdena till en Index och från dem skapar en ny Range instans. Följande kod visar ett exempel på implicit konvertering från syntaxen för intervalloperatorns uttryck och dess motsvarande explicita alternativ:

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'

Viktigt!

Implicita konverteringar från Int32 till att Index utlösa en ArgumentOutOfRangeException när värdet är negativt. Index På samma sätt genererar konstruktorn en ArgumentOutOfRangeException när parametern value är negativ.

Typstöd för index och intervall

Index och intervall ger tydlig, koncis syntax för åtkomst till ett enda element eller ett område med element i en sekvens. Ett indexuttryck returnerar vanligtvis typen av element i en sekvens. Ett intervalluttryck returnerar vanligtvis samma sekvenstyp som källsekvensen.

Alla typer som tillhandahåller en indexerare med en Index eller Range -parameter stöder uttryckligen index respektive intervall. En indexerare som tar en enskild Range parameter kan returnera en annan sekvenstyp, till exempel System.Span<T>.

Viktigt!

Prestanda för kod med hjälp av intervalloperatorn beror på typen av sekvensoperator.

Tidskomplexiteten för intervalloperatorn beror på sekvenstypen. Om sekvensen till exempel är en string eller en matris är resultatet en kopia av det angivna avsnittet av indata, så tidskomplexiteten är O(N) (där N är intervallets längd). Å andra sidan, om det är en System.Span<T> eller en System.Memory<T>, refererar resultatet till samma lagringsplats, vilket innebär att det inte finns någon kopia och åtgärden är O(1).

Förutom tidskomplexiteten orsakar detta extra allokeringar och kopior, vilket påverkar prestandan. I prestandakänslig kod bör du överväga att använda Span<T> eller Memory<T> som sekvenstyp, eftersom intervalloperatorn inte allokerar för dem.

En typ kan räknas om den har en egenskap med namnet Length eller Count med en tillgänglig getter och en returtyp av int. En countable-typ som inte uttryckligen stöder index eller intervall kan ge ett implicit stöd för dem. Mer information finns i avsnitten implicit indexstöd och implicita intervallstöd i funktionsförslagsanteckningen. Intervall med implicit intervallstöd returnerar samma sekvenstyp som källsekvensen.

Följande .NET-typer stöder till exempel både index och intervall: String, Span<T>och ReadOnlySpan<T>. Stöder List<T> index men stöder inte intervall.

Array har mer nyanserat beteende. Matriser med en dimension stöder både index och intervall. Flerdimensionella matriser stöder inte indexerare eller intervall. Indexeraren för en flerdimensionell matris har flera parametrar, inte en enda parameter. Ojämna matriser, även kallade matriser, stöder både intervall och indexerare. I följande exempel visas hur du itererar ett rektangulärt underavsnitt av en ojämn matris. Det itererar avsnittet i mitten, exklusive de första och sista tre raderna, och de första och sista två kolumnerna från varje vald rad:

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

I samtliga fall allokerar intervalloperatorn en matris för Array att lagra de element som returneras.

Scenarier för index och intervall

Du använder ofta intervall och index när du vill analysera en del av en större sekvens. Den nya syntaxen är tydligare vid läsning av exakt vilken del av sekvensen som ingår. Den lokala funktionen MovingAverage tar ett Range som argument. Metoden räknar sedan upp just det intervallet vid beräkning av min, max och medelvärde. Prova följande kod i projektet:

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

En anteckning om intervallindex och matriser

När du tar ett intervall från en matris är resultatet en matris som kopieras från den ursprungliga matrisen i stället för att refereras till. Om du ändrar värden i den resulterande matrisen ändras inte värdena i den inledande matrisen.

Till exempel:

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

Se även