Freigeben über


Indizes und Bereiche

Bereiche und Indizes bieten eine prägnante Syntax für den Zugriff auf einzelne Elemente oder Bereiche in einer Sequenz.

In diesem Tutorial lernen Sie Folgendes:

  • Verwenden Sie die Syntax für Bereiche in einer Sequenz.
  • Implizit definieren Sie eine Range.
  • Verstehen sie die Entwurfsentscheidungen für den Anfang und das Ende jeder Sequenz.
  • Lernen Sie Szenarien für die Typen Index und Range kennen.

Sprachunterstützung für Indizes und Bereiche

Indizes und Bereiche bieten eine prägnante Syntax für den Zugriff auf einzelne Elemente oder Bereiche in einer Sequenz.

Diese Sprachunterstützung basiert auf zwei neuen Typen und zwei neuen Operatoren:

Beginnen wir mit den Regeln für Indizes. Betrachten Sie ein Array sequence. Der 0 Index ist identisch mit sequence[0]. Der ^0 Index ist identisch mit sequence[sequence.Length]. Der Ausdruck sequence[^0] löst eine Ausnahme aus, genau wie sequence[sequence.Length] dies der Fall ist. Bei einer beliebigen Zahl nist der Index ^n identisch mit sequence.Length - n.

private string[] words = [
                // index from start     index from end
    "first",    // 0                    ^10
    "second",   // 1                    ^9
    "third",    // 2                    ^8
    "fourth",   // 3                    ^7
    "fifth",    // 4                    ^6
    "sixth",    // 5                    ^5
    "seventh",  // 6                    ^4
    "eighth",   // 7                    ^3
    "ninth",    // 8                    ^2
    "tenth"     // 9                    ^1
];              // 10 (or words.Length) ^0

Sie können das letzte Wort mit dem ^1 Index abrufen. Fügen Sie den folgenden Code unter der Initialisierung hinzu:

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

Ein Bereich gibt den Anfang und das Ende eines Bereichs an. Der Anfang des Bereichs ist inklusive, aber das Ende des Bereichs ist exklusiv, d. h., der Anfang ist im Bereich enthalten, aber das Ende ist nicht im Bereich enthalten. Der Bereich [0..^0] stellt den gesamten Bereich wie [0..sequence.Length] den gesamten Bereich dar.

Der folgende Code erstellt einen Unterbereich mit den Wörtern "second", "third" und "fourth". Es umfasst words[1] bis words[3]. Das Element words[4] befindet sich nicht im Bereich.

string[] secondThirdFourth = words[1..4]; // contains "second", "third" and "fourth"

// < second >< third >< fourth >
foreach (var word in secondThirdFourth)
    Console.Write($"< {word} >"); 
Console.WriteLine();

Der folgende Code gibt den Bereich mit "neunter" und "zehnter" zurück. Es enthält words[^2] und words[^1]. Der Endindex words[^0] ist nicht enthalten.

 string[] lastTwo = words[^2..^0]; // contains "ninth" and "tenth"

 // < ninth >< tenth >
 foreach (var word in lastTwo)
     Console.Write($"< {word} >"); 
 Console.WriteLine();

Die folgenden Beispiele erstellen Bereiche, die entweder am Anfang, am Ende oder an beiden Seiten offen sind:

string[] allWords = words[..]; // contains "first" through "tenth".
string[] firstPhrase = words[..4]; // contains "first" through "fourth"
string[] lastPhrase = words[6..]; // contains "seventh", "eight", "ninth" and "tenth"

// < first >< second >< third >< fourth >< fifth >< sixth >< seventh >< eighth >< ninth >< tenth >
foreach (var word in allWords)
    Console.Write($"< {word} >"); 
Console.WriteLine();

// < first >< second >< third >< fourth >
foreach (var word in firstPhrase)
    Console.Write($"< {word} >"); 
Console.WriteLine();

// < seventh >< eighth >< ninth >< tenth >
foreach (var word in lastPhrase)
    Console.Write($"< {word} >"); 
Console.WriteLine();

Sie können Bereiche oder Indizes auch als Variablen deklarieren. Die Variable kann dann innerhalb der Zeichen [ und ] verwendet werden.

Index thirdFromEnd = ^3;
Console.WriteLine($"< {words[thirdFromEnd]} > "); // < eighth > 
Range phrase = 1..4;
string[] text = words[phrase];

// < second >< third >< fourth >
foreach (var word in text)
    Console.Write($"< {word} >");  
Console.WriteLine();

Das folgende Beispiel zeigt viele der Gründe für diese Auswahl. Ändern Sie x, y und z, um verschiedene Kombinationen auszuprobieren. Wenn Sie experimentieren, verwenden Sie Werte, bei denen x weniger als yist, und y ist kleiner als z für gültige Kombinationen. Fügen Sie den folgenden Code in einer neuen Methode hinzu. Probieren Sie verschiedene Kombinationen aus:

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

Nicht nur Arrays unterstützen Indizes und Bereiche. Sie können auch Indizes und Bereiche mit String, Span<T> oder ReadOnlySpan<T> verwenden.

Konvertierungen von Ausdrücken mit impliziten Bereichsoperatoren

Bei der Verwendung der Syntax des Bereichsoperatorausdrucks konvertiert der Compiler implizit die Start- und Endwerte in ein Index, und aus diesen erstellt er eine neue Range-Instanz. Der folgende Code zeigt eine implizite Beispielkonvertierung aus der Syntax des Bereichsoperatorausdrucks und die entsprechende explizite Alternative:

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'

Von Bedeutung

Implizite Konvertierungen von Int32 zu Index werfen eine ArgumentOutOfRangeException, wenn der Wert negativ ist. Ebenso löst der Index-Konstruktor ein ArgumentOutOfRangeException aus, wenn der value-Parameter negativ ist.

Typunterstützung für Indizes und Bereiche

Indizes und Bereiche bieten klare, präzise Syntax für den Zugriff auf ein einzelnes Element oder einen Bereich von Elementen in einer Sequenz. Ein Indexausdruck gibt in der Regel den Typ der Elemente einer Sequenz zurück. Ein Bereichsausdruck gibt in der Regel denselben Sequenztyp wie die Quellsequenz zurück.

Jeder Typ, der einen Indexer mit einem Index Oder Range Parameter bereitstellt, unterstützt explizit Indizes oder Bereiche. Ein Indexer, der einen einzelnen Range Parameter verwendet, kann einen anderen Sequenztyp zurückgeben, wie System.Span<T>.

Von Bedeutung

Die Leistung des Codes mithilfe des Bereichsoperators hängt vom Typ des Sequenzoperanden ab.

Die Zeitkomplexität des Bereichsoperators hängt vom Sequenztyp ab. Wenn z. B. die Sequenz ein string oder ein Array ist, ist das Ergebnis eine Kopie des angegebenen Abschnitts der Eingabe, sodass die Zeitkomplexität O(N) ist (wobei N die Länge des Bereichs ist). Wenn es sich um ein System.Span<T> oder ein System.Memory<T>handelt, verweist das Ergebnis auf denselben Sicherungsspeicher, was bedeutet, dass keine Kopie vorhanden ist und der Vorgang O(1) ist.

Zusätzlich zur Zeitkomplexität verursacht dies zusätzliche Zuordnungen und Kopien, was sich auf die Leistung auswirkt. Berücksichtigen Sie bei leistungssensiblem Code die Verwendung von Span<T> oder Memory<T> als Sequenztyp, da der Range-Operator für sie keine Speicherzuweisungen vornimmt.

Ein Typ ist zählbar, wenn er eine Eigenschaft namens Length oder Count mit einem zugänglichen Getter und einem Rückgabetyp int aufweist. Ein zählbarer Typ, der Indizes oder Bereiche nicht explizit unterstützt, bietet möglicherweise eine implizite Unterstützung für sie. Weitere Informationen finden Sie in den Abschnitten "Impliziter Index-Unterstützung" und "Impliziter Bereich-Unterstützung" der Vorschlagsnotiz. Bereiche, die implizite Bereichsunterstützung verwenden, geben denselben Sequenztyp wie die Quellsequenz zurück.

Die folgenden .NET-Typen unterstützen z. B. sowohl Indizes als auch Bereiche: String, , Span<T>und ReadOnlySpan<T>. Die List<T> unterstützt Indizes, aber keine Bereiche.

Array hat ein differenzierteres Verhalten. Eindimensionale Arrays unterstützen sowohl Indizes als auch Bereiche. Mehrdimensionale Arrays unterstützen keine Indexer oder Bereiche. Der Indexer für ein mehrdimensionales Array hat mehrere Parameter, nicht einen einzelnen Parameter. Gezackte Arrays, auch als ein Array aus Arrays bezeichnet, unterstützen sowohl Bereiche als auch Indexer. Das folgende Beispiel zeigt, wie Sie einen rechteckigen Teilbereich eines gezackten Arrays durchlaufen. Es wird über den Bereich in der Mitte iteriert, ohne die ersten und letzten drei Zeilen und die ersten und letzten beiden Spalten aus jeder ausgewählten Zeile:

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 allen Fällen weist der Bereichsoperator Array ein Array zum Speichern der zurückgegebenen Elemente zu.

Szenarien für Indizes und Bereiche

Sie verwenden häufig Bereiche und Indizes, wenn Sie einen Teil einer größeren Sequenz analysieren möchten. Die neue Syntax macht es beim Lesen deutlicher, welcher Teil der Sequenz betroffen ist. Die lokale Funktion MovingAverage verwendet ein Range Argument. Die Methode listet dann nur diesen Bereich auf, wenn der Min-, Max- und Mittelwert berechnet wird. Probieren Sie den folgenden Code in Ihrem Projekt aus:

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

Eine Notiz zu Bereichsindizes und Arrays

Bei der Entnahme eines Bereichs aus einem Array besteht das Ergebnis aus einem Array, das aus dem ursprünglichen Array kopiert wird, statt referenziert zu werden. Durch das Ändern von Werten im resultierenden Array werden keine Werte im ursprünglichen Array geändert.

Beispiel:

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

Siehe auch