Indexy a rozsahy
Rozsahy a indexy poskytují stručnou syntaxi pro přístup k jednoduchým prvkům nebo rozsahům v posloupnosti.
V tomto kurzu se naučíte:
Podpora jazyka pro indexy a rozsahy
Indexy a rozsahy poskytují stručnou syntaxi pro přístup k jednoduchým prvkům nebo rozsahům v posloupnosti.
Podpora tohoto jazyka spoléhá na dva nové typy a dva nové operátory:
- System.Index představuje index do sekvence.
- Index z koncového operátoru
^
, který určuje, že index je relativní ke konci sekvence. - System.Range představuje podsestavu sekvence.
- Operátor
..
rozsahu , který určuje začátek a konec rozsahu jako jeho operandy.
Začněme pravidly indexů. Zvažte pole sequence
. Index 0
je stejný jako sequence[0]
. Index ^0
je stejný jako sequence[sequence.Length]
. Výraz sequence[^0]
vyvolá výjimku, stejně jako sequence[sequence.Length]
tomu je. U libovolného čísla n
je index ^n
stejný jako 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
Poslední slovo můžete načíst pomocí indexu ^1
. Pod inicializaci přidejte následující kód:
Console.WriteLine($"The last word is {words[^1]}");
Oblast určuje začátek a konec rozsahu. Začátek rozsahu je inkluzivní, ale konec rozsahu je výhradní, což znamená , že začátek je součástí rozsahu, ale konec není zahrnut do rozsahu. [0..^0]
Rozsah představuje celou oblast, stejně jako [0..sequence.Length]
celý rozsah.
Následující kód vytvoří podrange se slovy "quick", "brown" a "fox". Zahrnuje prostřednictvím words[1]
words[3]
. Prvek words[4]
není v rozsahu.
string[] quickBrownFox = words[1..4];
foreach (var word in quickBrownFox)
Console.Write($"< {word} >");
Console.WriteLine();
Následující kód vrátí rozsah s "opožděným" a "psem". words[^2]
Zahrnuje a words[^1]
. Koncový index words[^0]
není zahrnutý. Přidejte také následující kód:
string[] lazyDog = words[^2..^0];
foreach (var word in lazyDog)
Console.Write($"< {word} >");
Console.WriteLine();
Následující příklady vytvářejí otevřené oblasti pro začátek, konec nebo obojí:
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();
Rozsahy nebo indexy můžete deklarovat také jako proměnné. Proměnnou je pak možné použít uvnitř [
znaků a ]
znaků:
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();
Následující ukázka ukazuje řadu důvodů pro tyto volby. Upravit x
, y
a z
vyzkoušet různé kombinace. Při experimentování použijte hodnoty, kde x
je menší než y
, a y
je menší než z
pro platné kombinace. Do nové metody přidejte následující kód. Vyzkoušejte různé kombinace:
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]}");
Nejen pole podporují indexy a rozsahy. Můžete také použít indexy a rozsahy s řetězcem, Span<T>nebo ReadOnlySpan<T>.
Převody výrazů implicitního operátoru rozsahu
Při použití syntaxe výrazu operátoru rozsahu kompilátor implicitně převede počáteční a koncové hodnoty na Index a z nich vytvoří novou Range instanci. Následující kód ukazuje příklad implicitního převodu ze syntaxe výrazu operátoru rozsahu a odpovídající explicitní alternativu:
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'
Důležité
Implicitní převody z Int32 hodnoty na Index vyvolání, když je hodnota záporná ArgumentOutOfRangeException . Stejně tak Index
konstruktor vyvolá ArgumentOutOfRangeException
, když value
je parametr záporný.
Podpora typů pro indexy a rozsahy
Indexy a rozsahy poskytují jasnou a stručnou syntaxi pro přístup k jednomu prvku nebo rozsahu prvků v posloupnosti. Výraz indexu obvykle vrací typ prvků sekvence. Výraz rozsahu obvykle vrací stejný typ sekvence jako zdrojová sekvence.
Jakýkoli typ, který poskytuje indexer s parametrem Index nebo Range explicitně podporuje indexy nebo rozsahy. Indexer, který přebírá jeden Range parametr, může vrátit jiný typ sekvence, například System.Span<T>.
Důležité
Výkon kódu pomocí operátoru rozsahu závisí na typu sekvenčního operandu.
Časová složitost operátoru rozsahu závisí na typu sekvence. Pokud je například sekvence nebo string
matice, pak je výsledkem kopie zadané části vstupu, takže časová složitost je O(N) (kde N je délka rozsahu). Na druhou stranu, pokud je System.Span<T> to nebo , System.Memory<T>výsledek odkazuje na stejné záložní úložiště, což znamená, že neexistuje žádná kopie a operace je O(1).
Kromě složitosti času to způsobuje dodatečné přidělení a kopie, což má vliv na výkon. V kódu citlivém na výkon zvažte použití Span<T>
nebo Memory<T>
jako typ sekvence, protože operátor rozsahu pro ně nepřiděluje.
Typ lze spočítat , pokud má vlastnost s názvem Length
nebo Count
s přístupným getterem a návratovým typem int
. Počítaný typ, který explicitně nepodporuje indexy nebo rozsahy, může poskytnout implicitní podporu pro ně. Další informace najdete v částech podpory implicitního indexu a podpory implicitního rozsahu v poznámce návrhu funkce. Rozsahy používající implicitní rozsahy vracejí stejný typ sekvence jako zdrojová sekvence.
Například následující typy .NET podporují indexy i rozsahy: String, Span<T>a ReadOnlySpan<T>. Podporované List<T> indexy, ale nepodporují rozsahy.
Array má více nuancí chování. Pole s jednou dimenzí podporují indexy i rozsahy. Multidimenzionální pole nepodporují indexery ani rozsahy. Indexer pro vícerozměrné pole má více parametrů, nikoli jeden parametr. Jagged arrays, označované také jako pole polí, podporují rozsahy i indexery. Následující příklad ukazuje, jak iterovat obdélníkový pododdíl zahlceného pole. Iteruje oddíl uprostřed, s výjimkou prvních a posledních tří řádků a prvních a posledních dvou sloupců z každého vybraného řádku:
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();
}
Ve všech případech operátor rozsahu pro Array přidělení pole pro uložení vrácených prvků.
Scénáře pro indexy a rozsahy
Rozsahy a indexy často použijete, když chcete analyzovat část větší sekvence. Nová syntaxe je jasnější při čtení přesně toho, jakou část sekvence se týká. Místní funkce MovingAverage
přebírá Range jako svůj argument. Metoda pak vypočítá pouze tento rozsah při výpočtu min, max a průměru. Vyzkoušejte v projektu následující kód:
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))];
Poznámka k indexům rozsahu a polím
Při přebírání oblasti z pole je výsledkem matice, která se zkopíruje z počátečního pole, a ne z odkazu. Úprava hodnot ve výsledném poli nezmění hodnoty v počátečním poli.
Příklad:
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