Quick Reference
^n은 끝에서 n번째 위치를, start..end는 시작 포함·끝 제외 범위를 뜻합니다. 배열과 문자열의 range 결과는 복사본이지만 Span<T>/Memory<T>의 range는 같은 backing storage를 보는 view입니다. List<T>는 ^ 인덱스는 지원하지만 .. range는 지원하지 않으므로 같은 문법으로 일반화하지 않습니다.
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..]; // "World!"문법
^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>는 range를 지원하지만 결과와 비용은 서로 다릅니다. 배열·문자열은 새 값을 복사하고, Span<T>·Memory<T>는 원본 저장소의 일부를 가리킵니다. List<T>는 Index는 지원하지만 Range 인덱서를 제공하지 않습니다.
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]; // 할당 없음커스텀 타입에 인덱서 추가 — 지원 패턴을 직접 갖춘다
클래스나 구조체에 인덱서를 정의하면 배열처럼 [] 문법으로 요소에 접근할 수 있습니다. Index/Range는 모든 타입에 자동으로 생기지 않습니다. Index 또는 Range 인덱서를 직접 제공하거나, Length/Count와 int 인덱서·Slice(int, int)처럼 언어가 인식하는 pattern을 갖춘 경우에만 해당 문법을 쓸 수 있습니다.
public class WordList
{
private readonly string[] _words;
public WordList(params string[] words) => _words = words;
// 기본 인덱서
public string this[int index] => _words[index];
// Count/Length + int indexer로 ^n의 offset을 계산할 수 있음
public int Length => _words.Length;
// Range 지원을 위한 Slice 패턴
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 자체는 시작과 끝을 담는 값입니다. 실제 offset과 길이는 대상의 길이를 기준으로 계산하며, GetOffsetAndLength는 범위가 잘못됐을 때 ArgumentOutOfRangeException을 던집니다. arr[start..end]가 항상 Slice 호출로 바뀌는 것은 아닙니다. 배열·문자열·Span은 각자의 range 지원을 사용하고, 커스텀 타입만 해당 pattern이나 명시적 인덱서로 참여합니다.
int[] arr = [1, 2, 3, 4, 5];
Span<int> s = arr.AsSpan();
Range range = 1..^1;
(int offset, int length) = range.GetOffsetAndLength(arr.Length);
int[] copied = arr[range]; // 배열은 새 배열을 할당해 복사
// Span — 같은 저장소를 보는 view
Span<int> part1 = s[1..4];
Span<int> part2 = s.Slice(1, 3);Index와 Range 형태
| 상황 | 적합한 선택 |
|---|---|
| 마지막 요소 접근 | 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]은 범위를 벗어나는 인덱스 오류가 발생합니다.
참고 링크
2 sources