索引和範圍
範圍和索引提供簡潔的語法,以存取序列中的單一專案或範圍。
在本教學課程中,您將了解如何:
索引和範圍的語言支援
索引和範圍提供簡潔的語法,以存取序列中的單一元素或範圍。
此語言支援依賴兩個新的類型和兩個新的運算子:
- System.Index 代表序列的索引。
- end 運算子
^
的 索引,指定索引相對於序列結尾。 - System.Range 代表序列的子範圍。
- 範圍 運算子
..
,指定範圍的開始和結尾做為其運算元。
讓我們從索引的規則開始。 假設有一個陣列 sequence
。 0
索引與 sequence[0]
相同。 ^0
索引與 sequence[sequence.Length]
相同。 運算式 sequence[^0]
會擲回例外狀況,就像 sequence[sequence.Length]
這樣。 針對任何數字 n
,索引 ^n
與 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
您可以使用 ^1
索引擷取最後一個字。 將下列程式碼新增於初始設定下方:
Console.WriteLine($"The last word is {words[^1]}");
指定範圍「開頭」與「結尾」的範圍。 範圍的開頭是內含的,但範圍的結尾是獨佔的,這表示 開始 包含在範圍中,但 結尾 不包含在範圍中。 範圍 [0..^0]
代整個範圍,就像 [0..sequence.Length]
代表整個範圍一樣。
下列程式碼會建立具有 "quick"、"brown" 和 "fox" 字組的子範圍。 其會包含 words[1]
到 words[3]
。 項目 words[4]
不在範圍內。
string[] quickBrownFox = words[1..4];
foreach (var word in quickBrownFox)
Console.Write($"< {word} >");
Console.WriteLine();
下列程式碼會傳回 「lazy」 和 「dog」 的範圍。 其包含 words[^2]
及 words[^1]
。 未包含結尾索引 words[^0]
。 同時新增下列程式碼:
string[] lazyDog = words[^2..^0];
foreach (var word in lazyDog)
Console.Write($"< {word} >");
Console.WriteLine();
下列範例會建立不限定開始、結束或兩者的範圍:
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();
您也可以將範圍或索引宣告為變數。 然後即可在 [
和 ]
字元內使用變數:
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();
下列範例顯示這些選擇的許多原因。 修改 x
、y
和 z
以嘗試不同的組合。 在您實驗時,請使用 x
小於 y
,且 y
小於 z
的有效組合值。 在新方法中新增下列程式碼。 嘗試不同的組合:
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]}");
不僅陣列支援索引和範圍。 您也可以使用索引和範圍搭配 字串 、 Span<T> 或 ReadOnlySpan<T> 。
隱含範圍運算子運算式轉換
使用 range 運算子運算式語法時,編譯器會隱含地將開始和結束值 Index 轉換成 ,並從中建立新的 Range 實例。 下列程式碼顯示範圍運算子運算式語法的隱含轉換範例,以及其對應的明確替代方法:
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'
重要
當值為負數時,從 隱含轉換 Int32 至 Index 擲回 ArgumentOutOfRangeException 。 同樣地,建 Index
構函式會在 value
參數為負數時擲回 ArgumentOutOfRangeException
。
索引和範圍的類型支援
索引和範圍提供清楚、簡潔的語法,以存取序列中的單一專案或專案範圍。 索引運算式通常會傳回序列專案的類型。 範圍運算式通常會傳回與來源序列相同的序列類型。
以 或 Range 參數提供 索引子 Index 的任何類型,分別支援索引或範圍。 採用單 Range 一參數的索引子可能會傳回不同的序列類型,例如 System.Span<T> 。
重要
使用範圍運算子的程式碼效能取決於序列運算元的類型。
範圍運算子的時間複雜度取決於序列類型。 例如,如果序列是 string
或 陣列,則結果會是輸入指定區段的複本,因此時間複雜度為 O(N) (其中 N 是範圍的長度)。 另一方面,如果是 System.Span<T> 或 System.Memory<T> ,則結果會參考相同的備份存放區,這表示沒有複本,且作業為 O(1)。
除了時間複雜度之外,這也會造成額外的配置和複本,因而影響效能。 在效能敏感的程式碼中,請考慮使用 Span<T>
或 Memory<T>
作為序列類型,因為範圍運算子不會為其配置。
如果類型具有名為 Length
的屬性,或是 Count
具有可存取的 getter 和 的傳回型別,則類型是 可 計算的 int
。 未明確支援索引或範圍的可計算類型,可能會為其提供隱含支援。 如需詳細資訊,請參閱 功能提案附注 的 隱含索引支援 和 隱含範圍支援 小節。 使用隱含範圍支援的範圍會傳回與來源序列相同的序列類型。
例如,下列 .NET 類型同時支援索引和範圍: String 、 Span<T> 和 ReadOnlySpan<T> 。 List<T>支援索引,但不支援範圍。
Array 具有更細微的行為。 單一維度陣列同時支援索引和範圍。 多維度陣列不支援索引子或範圍。 多維度陣列的索引子具有多個參數,而不是單一參數。 Jagged 陣列也稱為陣列陣列,同時支援範圍和索引子。 下列範例示範如何反覆運算鋸齒狀陣列的矩形子區段。 它會逐一查看中央的 區段,不包括第一個和最後三個數據列,以及每個選取資料列的第一個和最後兩個數據行:
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();
}
在所有情況下,範圍運算子 Array 都會配置陣列來儲存傳回的專案。
索引和範圍的案例
當您想要分析較大序列的一部分時,您通常會使用範圍和索引。 新語法在確切閱讀序列涉及的哪個部分時更清楚。 區域函式 MovingAverage
採用 Range 作為其引數。 然後,方法在計算最小值、最大值和平均值時,僅會列舉該範圍。 請在您的專案中嘗試下列程式碼:
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))];
範圍索引和陣列的附注
從陣列擷取範圍時,結果是從初始陣列複製的陣列,而不是參考的陣列。 修改結果陣列中的值不會變更初始陣列中的值。
例如:
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