共用方式為


指數和範圍

範圍和索引提供簡潔的語法,用於存取序列中的單一元素或範圍。

在本教學課程中,您將瞭解如何:

  • 使用序列中範圍的語法。
  • 隱含定義 Range.
  • 瞭解每個序列開始和結束的設計決策。
  • 學習應用情境以了解 IndexRange 類型。

索引和範圍的語言支援

索引和範圍提供了一種簡潔的語法,用於訪問序列中的單個元素或範圍。

此語言支援依賴兩個新類型和兩個新運算子:

讓我們從指數的規則開始。 考慮一個陣列 sequence。 索引與 0sequence[0]相同。 索引與 ^0sequence[sequence.Length]相同。 運算式 sequence[^0] 會擲回例外狀況,就像 sequence[sequence.Length] 這樣做一樣。 對於任何數字 n,索引 ^nsequence.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

您可以使用 ^1 索引來擷取最後一個字。 在初始化下方新增下列程式碼:

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

範圍指定範圍的 開始結束 。 範圍的開頭是包含的,但範圍的結尾是排他的,這表示 開始 包含在範圍中,但 結束 不包含在範圍中。 [0..^0] 代表整個範圍,就像 [0..sequence.Length] 代表整個範圍一樣。

下列程式碼會建立包含 “second”、“third” 和 “fourth” 字樣的子範圍。 它包括 words[1]words[3]。 元素 words[4] 不在範圍內。

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

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

下列程式碼會傳回具有「第九」和「第十」的範圍。 它包括 words[^2]words[^1]。 不包括結束索引 words[^0]

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

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

下列範例會建立開頭、結尾或兩者的開放式範圍:

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

您也可以將範圍或索引宣告為變數。 然後,變數可以在 和 [] 字元內使用:

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

下列範例顯示這些選擇的許多原因。 修改 xyz以嘗試不同的組合。 當您進行實驗時,請使用值,其中 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>搭配使用。

隱含範圍運算子表達式轉換

使用範圍運算子運算式語法時,編譯器會隱含地將開始值和結束值轉換為 和 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'

這很重要

隱含轉換從 Int32Index 時,如果值為負數,則拋出 ArgumentOutOfRangeException。 同樣地,Index建構函式在value參數為負數時會擲回ArgumentOutOfRangeException

索引和範圍的類型支援功能

索引和範圍提供清晰、簡潔的語法來存取序列中的單一元素或一系列元素。 索引運算式通常會傳回序列元素的類型。 範圍運算式通常會傳回與來源序列相同的序列類型。

任何提供索引器並透過IndexRange參數來明確支援索引或範圍的類型。 採用單一 Range 參數的索引子可能會傳回不同的序列類型,例如 System.Span<T>

這很重要

使用範圍運算子的程式碼效能取決於序列運算元的類型。

範圍運算子的時間複雜度取決於序列類型。 例如,如果序列是 或 string 數組,則結果是輸入指定區段的副本,因此時間複雜度為 O(N) (其中 N 是範圍的長度)。 另一方面,如果它是 a System.Span<T> 或 a System.Memory<T>,則結果引用相同的後備存儲,這意味著沒有副本並且操作是 O(1)。

除了時間複雜度之外,這還會導致額外的記憶體分配和複製品,從而影響效能。 在效能敏感程式碼中,請考慮使用 Span<T>Memory<T> 作為序列類型,因為範圍運算子不會為它們配置。

如果類型具有名為LengthCount的屬性,並且該屬性具有可存取的 getter,其傳回類型為int,則此類型是可數的。 未明確支援索引或範圍的可數類型可能會為它們提供隱含支援。 如需詳細資訊,請參閱功能提案附註隱含索引支援隱含範圍支援區段。 使用隱含範圍支援的範圍會傳回與來源序列相同的序列類型。

例如,下列 .NET 類型同時支援索引和範圍: StringSpan<T>ReadOnlySpan<T>。 支援 List<T> 索引,但不支援範圍。

Array 有更細膩的特性表現。 單一維度陣列同時支援索引和範圍。 多維度陣列不支援索引子或範圍。 多維度陣列的索引子有多個參數,而不是單一參數。 鋸齒狀陣列,也稱為多維陣列,同時支援範圍和索引器。 下列範例示範如何遍歷鋸齒陣列的矩形子區段。 它會逐一查看中間的區段,不包括每個選取列中的第一和最後三列,以及第一和最後兩列:

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 採用 a 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

另請參閱