다음을 통해 공유


인덱스 및 범위

범위 및 인덱스는 시퀀스의 단일 요소 또는 범위에 액세스하기 위한 간결한 구문을 제공합니다.

이 자습서에서는 다음 작업을 수행하는 방법을 알아봅니다.

  • 시퀀스의 범위에 대한 구문을 사용합니다.
  • 암시적으로 Range을 정의하십시오.
  • 각 시퀀스의 시작 및 끝에 대한 디자인 결정을 이해합니다.
  • IndexRange 유형의 시나리오를 학습합니다.

인덱스 및 범위에 대한 언어 지원

인덱스 및 범위는 시퀀스의 단일 요소 또는 범위에 액세스하기 위한 간결한 구문을 제공합니다.

이 언어 지원은 두 가지 새 형식과 두 개의 새 연산자를 사용합니다.

  • System.Index 는 인덱스를 시퀀스로 나타냅니다.
  • 시퀀스의 끝을 기준으로 인덱스를 지정하는 끝 연산 ^자의 인덱스입니다.
  • System.Range 는 시퀀스의 하위 범위를 나타냅니다.
  • 범위 연산..자입니다. 범위의 시작과 끝을 피연산자로 지정합니다.

인덱스에 대한 규칙부터 시작해 보겠습니다. 배열 sequence을 고려합니다. 0 인덱스는 sequence[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();

다음 코드는 "9번째" 및 "10번째" 범위를 반환합니다. 포함 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();

다음 샘플에서는 이러한 선택에 대한 여러 가지 이유를 보여 줍니다. x를 수정하고, y를 수정하거나 z를 수정하여 다른 조합을 시도합니다. 실험할 때 유효한 조합을 위해 xy보다 작고, yz보다 작은 값을 사용하세요. 새 메서드에 다음 코드를 추가합니다. 다른 조합을 시도합니다.

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'

중요합니다

값이 음수일 때 Int32에서 Index로의 암시적 변환은 ArgumentOutOfRangeException를 던집니다. ** 마찬가지로, Index 생성자는 value 매개 변수가 음수일 때 ArgumentOutOfRangeException를 던집니다.

인덱스 및 범위에 대한 형식 지원

인덱스 및 범위는 시퀀스의 단일 요소 또는 요소 범위에 액세스하는 명확하고 간결한 구문을 제공합니다. 인덱스 식은 일반적으로 시퀀스의 요소 형식을 반환합니다. 범위 식은 일반적으로 소스 시퀀스와 동일한 시퀀스 형식을 반환합니다.

인덱서Index 또는 Range 매개 변수를 제공하는 모든 형식은 인덱스 또는 범위를 각각 명시적으로 지원합니다. 단일 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 형식은 인덱스와 범위를 모두 지원합니다. StringSpan<T>ReadOnlySpan<T> List<T> 인덱스를 지원하지만 범위를 지원하지 않습니다.

Array 에는 더 미묘한 동작이 있습니다. 단일 차원 배열은 인덱스와 범위를 모두 지원합니다. 다차원 배열은 인덱서 또는 범위를 지원하지 않습니다. 다차원 배열의 인덱서에는 단일 매개 변수가 아닌 여러 매개 변수가 있습니다. 가는 배열은 배열의 배열이라고도 하며, 범위와 인덱서를 모두 지원합니다. 다음 예제에서는 들쭉날쭉한 배열의 사각형 하위 섹션을 반복하는 방법을 보여 줍니다. 첫 번째 3개 행과 마지막 3개 행, 그리고 선택된 각 행의 첫 번째 및 마지막 두 열을 제외하고, 중앙의 섹션을 반복합니다.

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

참고하십시오