기본 패턴
int[] nums = [10, 20, 30, 40, 50];
// System.Index — ^n은 끝에서 n번째
int last = nums[^1]; // 50
int secondLast = nums[^2]; // 40
// System.Range — start..end (end는 포함 안 됨)
int[] first3 = nums[0..3]; // [10, 20, 30]
int[] last2 = nums[^2..]; // [40, 50]
int[] middle = nums[1..^1]; // [20, 30, 40]
int[] all = nums[..]; // [10, 20, 30, 40, 50] 전체 복사
// 문자열에도 동일하게 적용
string s = "Hello, World!";
string sub = s[7..12]; // "World"
string tail = s[^6..]; // "orld!"설명
^n Index가 필요한 이유 — 길이 계산을 컴파일러에 위임
배열의 마지막 요소를 구하려면 이전에는 arr[arr.Length - 1]처럼 직접 길이를 계산해야 했습니다. 이 패턴은 길이가 0일 때 예외를 발생시키고, 루프 안에서 반복되면 가독성도 떨어집니다. System.Index의 ^n 문법은 "끝에서 n번째"라는 의도를 직접 표현하고, 범위 계산을 컴파일러에 위임합니다.
string[] words = ["apple", "banana", "cherry", "date"];
// 이전 방식 — 직접 Length 계산
string last1 = words[words.Length - 1]; // "date"
string prev1 = words[words.Length - 2]; // "cherry"
// ✅ System.Index
string last2 = words[^1]; // "date"
string prev2 = words[^2]; // "cherry"
// Index를 변수로 저장해서 재사용 가능
Index fromEnd = ^1;
string last3 = words[fromEnd]; // "date"
// 컴파일러 변환 — ^n은 내부적으로 length - n으로 처리
// words[^1] → words[words.Length - 1]start..end Range — 슬라이싱으로 서브컬렉션 표현
System.Range는 시작 인덱스(포함)부터 끝 인덱스(미포함)까지의 범위를 표현합니다. 배열, 문자열, Span<T>, Memory<T> 모두에서 동작합니다. Substring, ArraySegment, Skip/Take 조합 없이 직관적인 슬라이스 표현이 가능합니다.
string text = "2026-04-04";
// 이전 방식
string year1 = text.Substring(0, 4); // "2026"
string month1 = text.Substring(5, 2); // "04"
string day1 = text.Substring(8, 2); // "04"
// ✅ Range
string year2 = text[0..4]; // "2026"
string month2 = text[5..7]; // "04"
string day2 = text[8..10]; // "04"
string rest = text[5..]; // "04-04" — 끝까지
string start = text[..4]; // "2026" — 처음부터
// Span과 조합 — 힙 할당 없이 슬라이스
ReadOnlySpan<char> span = text.AsSpan();
ReadOnlySpan<char> yearSpan = span[0..4]; // 할당 없음커스텀 타입에 인덱서 추가 — this[int index] 패턴
클래스나 구조체에 인덱서를 정의하면 배열처럼 [] 문법으로 요소에 접근할 수 있습니다. Index와 Range를 지원하는 패턴(Count/Length 프로퍼티 + Slice 메서드)을 구현하면 컴파일러가 자동으로 범위 접근을 지원합니다.
public class WordList
{
private readonly string[] _words;
public WordList(params string[] words) => _words = words;
// 기본 인덱서
public string this[int index] => _words[index];
// Index 지원 — ^n 사용 가능
public string this[Index index] => _words[index];
// Range 지원을 위한 Length + Slice 패턴
public int Length => _words.Length;
public string[] Slice(int start, int length)
=> _words[start..(start + length)];
}
var list = new WordList("apple", "banana", "cherry", "date");
string last = list[^1]; // "date"
string[] first2 = list[..2]; // ["apple", "banana"] — Slice 패턴 활용컴파일러 변환 원리 — Range는 Slice 호출로 바뀜
arr[start..end] 표현식은 컴파일러가 자동으로 arr.Slice(start, length) 또는 대상 타입에 맞는 메서드 호출로 변환합니다. GetRange나 AsSpan을 명시적으로 호출하지 않아도 되는 이유입니다. 배열, 문자열, Span<T>는 런타임이 직접 지원하고, 커스텀 타입은 Length + Slice 패턴으로 참여할 수 있습니다.
int[] arr = [1, 2, 3, 4, 5];
Span<int> s = arr.AsSpan();
// 아래 두 줄은 컴파일 후 동일한 IL
int[] sliced1 = arr[1..4]; // 컴파일러 변환
int[] sliced2 = arr[new Range(1, 4)]; // 명시적 Range
// Span — 별도 Slice 호출 없이 Range 사용
Span<int> part1 = s[1..4];
Span<int> part2 = s.Slice(1, 3); // 동일한 결과빠른 정리
| 상황 | 적합한 선택 |
|---|---|
| 마지막 요소 접근 | arr[^1] |
| 끝에서 n번째 | arr[^n] |
| 처음 n개 | arr[..n] |
| 마지막 n개 | arr[^n..] |
| i부터 j 직전까지 (슬라이스) | arr[i..j] |
| 전체 복사 | arr[..] |
| 힙 할당 없는 슬라이스 | span[i..j] 또는 str.AsSpan()[i..j] |
주의할 점
Range의 끝 인덱스는 포함되지 않습니다(upper-bound exclusive). Python의 슬라이싱과 동일한 규칙이지만, "마지막까지 포함"으로 착각하기 쉽습니다.
int[] nums = [10, 20, 30, 40, 50]; // 인덱스 0~4
// ❌ 3번 인덱스까지 포함하려는 의도였지만...
int[] wrong = nums[0..3]; // [10, 20, 30] — 인덱스 3(값 40)은 포함되지 않음
// ✅ 인덱스 3까지 포함하려면 끝을 4로 지정
int[] correct = nums[0..4]; // [10, 20, 30, 40]
// ✅ 또는 ^n으로 끝에서부터 역산
int[] last3 = nums[^3..]; // [30, 40, 50]또한 ^0은 arr.Length와 같습니다. arr[0..^0]은 전체 배열과 동일하며, arr[^0]은 범위를 벗어나는 인덱스 오류가 발생합니다.