Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Zakresy i indeksy zapewniają zwięzłą składnię dostępu do pojedynczych elementów lub zakresów w sekwencji.
Z tego samouczka dowiesz się, jak wykonywać następujące działania:
Obsługa językowa dotycząca indeksów i zakresów
Indeksy i zakresy zapewniają zwięzłą składnię dostępu do pojedynczych elementów lub zakresów w sekwencji.
Ta obsługa języka opiera się na dwóch nowych typach i dwóch nowych operatorach:
- System.Index reprezentuje indeks w sekwencji.
-
Indeks z operatora
^końcowego , który określa, że indeks jest względem końca sekwencji. - System.Range reprezentuje podzakres sekwencji.
-
Operator
..zakresu , który określa początek i koniec zakresu jako operandy.
Zacznijmy od reguł indeksów. Rozważ tablicę sequence. Indeks 0 jest taki sam jak sequence[0]. Indeks ^0 jest taki sam jak sequence[sequence.Length]. Wyrażenie sequence[^0] zgłasza wyjątek, podobnie jak sequence[sequence.Length]. W przypadku dowolnej liczby nindeks ^n jest taki sam jak 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
Można pobrać ostatnie słowo, używając indeksu ^1. Dodaj następujący kod poniżej inicjowania:
Console.WriteLine($"The last word is < {words[^1]} >."); // The last word is < tenth >.
Zakres określa początek i koniec zakresu. Początek zakresu jest włączony, ale koniec zakresu jest wyłączony, co oznacza, że początek jest uwzględniony w zakresie, ale koniec nie jest uwzględniony w zakresie. Zakres [0..^0] reprezentuje cały zakres, tak jak [0..sequence.Length] reprezentuje cały zakres.
Poniższy kod tworzy podzakres ze słowami "second", "third" i "fourth". Obejmuje od words[1] do words[3]. Element words[4] nie znajduje się w zakresie.
string[] secondThirdFourth = words[1..4]; // contains "second", "third" and "fourth"
// < second >< third >< fourth >
foreach (var word in secondThirdFourth)
Console.Write($"< {word} >");
Console.WriteLine();
Kod podany poniżej zwraca zakres zawierający "dziewiąty" i "dziesiąty". Zawiera on elementy words[^2] i words[^1]. Indeks words[^0] końcowy nie jest uwzględniony.
string[] lastTwo = words[^2..^0]; // contains "ninth" and "tenth"
// < ninth >< tenth >
foreach (var word in lastTwo)
Console.Write($"< {word} >");
Console.WriteLine();
W poniższych przykładach tworzone są otwarte zakresy dla początku, końca lub obu:
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();
Można również zadeklarować zakresy lub indeksy jako zmienne. Zmienna może być następnie używana wewnątrz znaków [ oraz ].
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();
Poniższy przykład przedstawia wiele przyczyn tych wyborów. Zmodyfikuj x, yi z , aby wypróbować różne kombinacje. Podczas eksperymentu użyj wartości, w których x wartość jest mniejsza niż y, i y jest mniejsza niż z w przypadku prawidłowych kombinacji. Dodaj następujący kod w nowej metodzie. Wypróbuj różne kombinacje:
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]}");
Nie tylko tablice obsługują indeksy i zakresy. Można również używać indeksów i zakresów z ciągami, Span<T>lub ReadOnlySpan<T>.
Niejawne konwersje wyrażeń operatorów zakresu
W przypadku używania składni wyrażenia operatora zakresu kompilator niejawnie konwertuje wartości początkowe i końcowe na element Index i z nich, tworzy nowe Range wystąpienie. Poniższy kod przedstawia przykładową niejawną konwersję ze składni wyrażenia operatora zakresu i odpowiadającą jej jawną alternatywę:
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'
Ważne
Niejawne konwersje z Int32 na Index powodują zgłoszenie ArgumentOutOfRangeException, gdy wartość jest ujemna. Podobnie, gdy parametr value ma wartość ujemną, konstruktor zgłasza ArgumentOutOfRangeException.
Obsługa typów indeksów i zakresów
Indeksy i zakresy zapewniają wyraźną, zwięzłą składnię dostępu do pojedynczego elementu lub zakresu elementów w sekwencji. Wyrażenie indeksu zwykle zwraca typ elementów sekwencji. Wyrażenie zakresu zwykle zwraca ten sam typ sekwencji co sekwencja źródłowa.
Każdy typ, który udostępnia indeksator z parametrem Index lub Range jawnie obsługuje indeksy lub zakresy odpowiednio. Indeksator, który przyjmuje pojedynczy Range parametr, może zwrócić inny typ sekwencji, taki jak System.Span<T>.
Ważne
Wydajność kodu przy użyciu operatora zakresu zależy od typu operandu sekwencji.
Złożoność czasowa operatora zakresu zależy od typu sekwencji. Na przykład jeśli sekwencja jest tablicą string lub tablicą, wynikiem jest kopia określonej sekcji danych wejściowych, więc złożoność czasu to O(N) (gdzie N jest długością zakresu). Z drugiej strony, jeśli jest to System.Span<T> lub System.Memory<T>, wynik odwołuje się do tego samego magazynu bazowego, co oznacza, że nie ma kopii, a operacja to O(1).
Oprócz złożoności czasowej, prowadzi to do dodatkowych alokacji i kopiowania, co wpływa na wydajność. W kodzie o dużej wrażliwości na wydajność, rozważ użycie Span<T> lub Memory<T> jako sekwencji, ponieważ operator zakresu nie przydziela pamięci dla nich.
Typ jest zliczalny, jeśli ma właściwość o nazwie Length lub Count z dostępnym getterem i typem zwracanym int. Typ zliczalny, który nie obsługuje jawnie indeksów lub zakresów, może zapewnić niejawną obsługę tych indeksów. Aby uzyskać więcej informacji, zobacz sekcje Obsługa niejawnego indeksu i Obsługa niejawnego zakresu w notatce propozycji funkcji. Zakresy używające obsługi niejawnego zakresu zwracają ten sam typ sekwencji co sekwencja źródłowa.
Na przykład następujące typy platformy .NET obsługują zarówno indeksy, jak i zakresy: String, Span<T>i ReadOnlySpan<T>. Obsługuje List<T> indeksy, ale nie obsługuje zakresów.
Array ma bardziej zniuansowane zachowanie. Tablice z pojedynczym wymiarem obsługują zarówno indeksy, jak i zakresy. Tablice wielowymiarowe nie obsługują indeksatorów ani zakresów. Indeksator tablicy wielowymiarowej ma wiele parametrów, a nie jeden parametr. Tablice postrzępione, nazywane również tablicą tablic, obsługują zarówno zakresy, jak i indeksatory. W poniższym przykładzie pokazano, jak iterować prostokątną podsekcję tablicy postrzępionej. Iteruje sekcję w środku, z wyłączeniem pierwszych i ostatnich trzech wierszy oraz pierwszych i ostatnich dwóch kolumn z każdego zaznaczonego wiersza:
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();
}
We wszystkich przypadkach operator zakresu Array przydziela tablicę do przechowywania zwróconych elementów.
Scenariusze dotyczące indeksów i zakresów
Często używasz zakresów i indeksów, gdy chcesz przeanalizować część większej sekwencji. Nowa składnia jest jaśniejsza w odczytywaniu dokładnie tej części sekwencji. Funkcja MovingAverage lokalna przyjmuje Range jako argument . Następnie metoda wylicza tylko ten zakres podczas obliczania wartości min, maksimum i średniej. Wypróbuj następujący kod w projekcie:
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))];
Uwaga dotycząca indeksów i tablic zakresów
Podczas wybierania zakresu z tablicy wynik jest tablicą skopiowaną z początkowej tablicy, a nie jej odwołaniem. Modyfikowanie wartości w wynikowej tablicy nie spowoduje zmiany wartości w początkowej tablicy.
Przykład:
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