インデックスと範囲Indices and ranges

範囲とインデックスには、シーケンス内の 1 つの要素または範囲にアクセスできる簡潔な構文が用意されています。Ranges and indices provide a succinct syntax for accessing single elements or ranges in a sequence.

このチュートリアルでは、次の作業を行う方法について説明します。In this tutorial, you'll learn how to:

  • シーケンス内の範囲に構文を使用します。Use the syntax for ranges in a sequence.
  • 各シーケンスの開始と終了に関する設計上の決定について説明します。Understand the design decisions for the start and end of each sequence.
  • Index 型と Range 型のシナリオについて説明します。Learn scenarios for the Index and Range types.

インデックスと範囲の言語サポートLanguage support for indices and ranges

この言語のサポートでは、次の 2 つの新しい型と 2 つの新しい演算子が使用されています。This language support relies on two new types and two new operators:

  • System.Index はシーケンスとしてインデックスを表します。System.Index represents an index into a sequence.
  • index from end 演算子の ^。シーケンスの末尾から相対的なインデックスを指定します。The index from end operator ^, which specifies that an index is relative to the end of a sequence.
  • System.Range はシーケンスのサブ範囲を表します。System.Range represents a sub range of a sequence.
  • 範囲演算子の ..。範囲の先頭と末尾をそのオペランドとして指定します。The range operator .., which specifies the start and end of a range as its operands.

インデックスのルールから始めましょう。Let's start with the rules for indices. 配列 sequence を考えます。Consider an array sequence. 0 インデックスは sequence[0] と同じです。The 0 index is the same as sequence[0]. ^0 インデックスは sequence[sequence.Length] と同じです。The ^0 index is the same as sequence[sequence.Length]. sequence[^0] 式からは、sequence[sequence.Length] の場合と同様に、例外がスローされます。The expression sequence[^0] does throw an exception, just as sequence[sequence.Length] does. 任意の数値 n の場合、インデックス ^nsequence[sequence.Length - n] と同じです。For any number n, the index ^n is the same as sequence[sequence.Length - n].

string[] words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0

末尾の単語は ^1 インデックスで取得できます。You can retrieve the last word with the ^1 index. 初期化の下に次のコードを追加します。Add the following code below the initialization:

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

範囲は、範囲の先頭末尾を指定します。A range specifies the start and end of a range. 範囲は排他的です。つまり、"末尾" は範囲に含まれません。Ranges are exclusive, meaning the end isn't included in the range. 範囲 [0..^0] は、[0..sequence.Length] が範囲全体を表すのと同じように、範囲全体を表します。The range [0..^0] represents the entire range, just as [0..sequence.Length] represents the entire range.

次のコードでは、単語 "quick"、"brown"、"fox" から成る部分範囲が作成されます。The following code creates a subrange with the words "quick", "brown", and "fox". それには、words[1] から words[3] までが含まれます。It includes words[1] through words[3]. 要素 words[4] が範囲内にありません。The element words[4] isn't in the range. 同じメソッドに次のコードを追加します。Add the following code to the same method. それをコピーして、対話型ウィンドウの下部に貼り付けます。Copy and paste it at the bottom of the interactive window.

string[] quickBrownFox = words[1..4];
foreach (var word in quickBrownFox)
    Console.Write($"< {word} >");
Console.WriteLine();

次のコードでは、"lazy" と "dog" の範囲が返されます。The following code returns the range with "lazy" and "dog". それには、words[^2]words[^1] が含まれます。It includes words[^2] and words[^1]. 末尾インデックス words[^0] は含まれません。The end index words[^0] isn't included. 次のコードも追加します。Add the following code as well:

string[] lazyDog = words[^2..^0];
foreach (var word in lazyDog)
    Console.Write($"< {word} >");
Console.WriteLine();

次の例では、先頭と末尾の一方または両方が開いている範囲が作成されます。The following examples create ranges that are open ended for the start, end, or both:

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

範囲やインデックスを変数として宣言することもできます。You can also declare ranges or indices as variables. この変数は、文字 [] の内側で使用できます。The variable can then be used inside the [ and ] characters:

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

次のサンプルは、こうした選択肢の理由の多くを示しています。The following sample shows many of the reasons for those choices. xyz を変更してさまざまな組み合わせを試してください。Modify x, y, and z to try different combinations. 実験するときには、有効な組み合わせになるように xy 未満の値、yz 未満の値を使用します。When you experiment, use values where x is less than y, and y is less than z for valid combinations. 新しいメソッドに次のコードを追加します。Add the following code in a new method. さまざまな組み合わせを試してください。Try different combinations:

int[] numbers = Enumerable.Range(0, 100).ToArray();
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]}");

インデックスと範囲の型のサポートType support for indices and ranges

インデックスと範囲を使用すると、シーケンス内の 1 つの要素または要素の範囲にアクセスする構文を明確かつ簡潔に指定できます。Indexes and ranges provide clear, concise syntax to access a single element or a range of elements in a sequence. インデックス式は、一般的に、シーケンスの要素の型を返します。An index expression typically returns the type of the elements of a sequence. 範囲式は、一般的に、ソース シーケンスと同じシーケンス型を返します。A range expression typically returns the same sequence type as the source sequence.

Index または Range パラメーターを持つインデクサーが用意されている型では、インデックスまたは範囲がそれぞれ明示的にサポートされます。Any type that provides an indexer with an Index or Range parameter explicitly supports indices or ranges respectively. 1 つの Range パラメーターをとるインデクサーからは、System.Span<T> などの別のシーケンス型が返される場合があります。An indexer that takes a single Range parameter may return a different sequence type, such as System.Span<T>.

重要

範囲演算子を使用したコードのパフォーマンスは、シーケンス オペランドの型によって異なります。The performance of code using the range operator depends on the type of the sequence operand.

範囲演算子の時間計算量は、シーケンスの種類によって異なります。The time complexity of the range operator depends on the sequence type. たとえば、シーケンスが string または配列の場合、指定したセクションの入力のコピーが結果として返されるため、時間計算量は O(N) になります (ここで N は範囲の長さです)。For example, if the sequence is a string or an array, then the result is a copy of the specified section of the input, so the time complexity is O(N) (where N is the length of the range). 一方、System.Span<T> または System.Memory<T> の場合、結果で同じバッキング ストアが参照されます。つまり、コピーは行われず、操作は O(1) になります。On the other hand, if it's a System.Span<T> or a System.Memory<T>, the result references the same backing store, which means there is no copy and the operation is O(1).

これでは時間計算量以外に、パフォーマンスに影響する割り当てとコピーも追加で発生します。In addition to the time complexity, this causes extra allocations and copies, impacting performance. パフォーマンスが重視されるコードでは、シーケンス型として範囲演算子が割り当てられない Span<T> または Memory<T> を使用することを検討してください。In performance sensitive code, consider using Span<T> or Memory<T> as the sequence type, since the range operator does not allocate for them.

アクセス可能なゲッターと戻り値の型 int を持つ Length または Count という名前のプロパティがある場合、型は可算です。A type is countable if it has a property named Length or Count with an accessible getter and a return type of int. インデックスまたは範囲を明示的にサポートしていない可算型は、それらを暗黙的にサポートしている可能性があります。A countable type that doesn't explicitly support indices or ranges may provide an implicit support for them. 詳細については、機能の提案に関する注記の「暗黙的なインデックスのサポート」と「暗黙的な範囲のサポート」のセクションを参照してください。For more information, see the Implicit Index support and Implicit Range support sections of the feature proposal note. 暗黙的な範囲のサポートを使用している範囲によって返されるのは、ソース シーケンスと同じシーケンス型です。Ranges using implicit range support return the same sequence type as the source sequence.

たとえば、次の .NET 型ではインデックスと範囲の両方がサポートされています: StringSpan<T>、および ReadOnlySpan<T>For example, the following .NET types support both indices and ranges: String, Span<T>, and ReadOnlySpan<T>. List<T> はインデックスをサポートしていますが、範囲はサポートしていません。The List<T> supports indices but doesn't support ranges.

Array には、より微妙な動作があります。Array has more nuanced behavior. 1 次元配列では、インデックスと範囲の両方がサポートされます。Single dimension arrays support both indices and ranges. 多次元配列ではそうではありません。Multi-dimensional arrays don't. 多次元配列のインデクサーには、1 つのパラメーターではなく、複数のパラメーターがあります。The indexer for a multi-dimensional array has multiple parameters, not a single parameter. 配列の配列とも呼ばれるジャグ配列では、範囲とインデクサーの両方がサポートされます。Jagged arrays, also referred to as an array of arrays, support both ranges and indexers. 次の例では、ジャグ配列の四角形サブセクションを反復処理する方法を示しています。The following example shows how to iterate a rectangular subsection of a jagged array. 最初と最後の 3 つの行と、選択された各行の最初と最後の 2 つの列を除いて、中央のセクションが反復処理されます。It iterates the section in the center, excluding the first and last three rows, and the first and last two columns from each selected row:

var jagged = new int[10][]
{
   new int[10] { 0,  1, 2, 3, 4, 5, 6, 7, 8, 9},
   new int[10] { 10,11,12,13,14,15,16,17,18,19},
   new int[10] { 20,21,22,23,24,25,26,27,28,29},
   new int[10] { 30,31,32,33,34,35,36,37,38,39},
   new int[10] { 40,41,42,43,44,45,46,47,48,49},
   new int[10] { 50,51,52,53,54,55,56,57,58,59},
   new int[10] { 60,61,62,63,64,65,66,67,68,69},
   new int[10] { 70,71,72,73,74,75,76,77,78,79},
   new int[10] { 80,81,82,83,84,85,86,87,88,89},
   new int[10] { 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 の範囲演算子では、返される要素を格納する配列が割り当てられます。In all cases, the range operator for Array allocates an array to store the elements returned.

インデックスと範囲のシナリオScenarios for indices and ranges

長いシーケンスの部分を分析するときは、多くの場合、範囲とインデックスを使用します。You'll often use ranges and indices when you want to analyze a portion of a larger sequence. 新しい構文では、シーケンスのどの部分が関係しているかをより正確に読み取ることができます。The new syntax is clearer in reading exactly what portion of the sequence is involved. ローカル関数 MovingAverage は、引数として Range を受け取ります。The local function MovingAverage takes a Range as its argument. このメソッドでは、最小値、最大値、および平均値を計算するときに、その範囲のみが列挙されます。The method then enumerates just that range when calculating the min, max, and average. プロジェクトで次のコードを試してみてください。Try the following code in your project:

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