Quick Reference
컬렉션 식 [...]은 C# 12의 대상 타입 의존 표현식입니다. 배열·List<T>·Span<T>처럼 대상 타입이 분명할 때 짧게 만들 수 있고, .. spread는 원본을 수정하지 않고 요소를 새 대상에 넣습니다. 할당·복사·구체 타입은 대상 타입과 spread 원본에 따라 달라지므로 성능 약속으로 쓰지 않습니다.
// 동일한 문법으로 다양한 컬렉션 타입 초기화
int[] arr = [1, 2, 3];
List<int> list = [1, 2, 3];
Span<int> span = [1, 2, 3];
IEnumerable<int> seq = [1, 2, 3];
// spread 연산자 .. 로 컬렉션 병합
int[] first = [1, 2, 3];
int[] second = [4, 5, 6];
int[] merged = [..first, ..second, 7]; // [1, 2, 3, 4, 5, 6, 7]
// 빈 컬렉션
List<string> empty = [];문법
어떤 컬렉션에 바로 쓸 수 있나
컬렉션 식은 대상 타입이 분명한 경우에 아래처럼 바로 쓸 수 있습니다.
int[] items = [1, 2, 3];
List<int> numbers = [1, 2, 3];
Span<int> window = [1, 2, 3];
IEnumerable<int> sequence = [1, 2, 3];- 배열
List<T>Span<T>/ReadOnlySpan<T>- 컬렉션을 받을 수 있는 인터페이스 타입
통일된 문법이 필요한 이유 — 타입별로 달랐던 초기화 방식
이전까지는 컬렉션 타입마다 초기화 문법이 달랐습니다. 배열은 new int[] { }, 리스트는 new List<int> { }, 빈 컬렉션은 Array.Empty<T>() 또는 Enumerable.Empty<T>(). 코드 리뷰어는 맥락 없이는 의도를 추론해야 했고, 타입을 바꿀 때마다 초기화 코드도 함께 수정해야 했습니다.
컬렉션 식은 [...] 하나로 여러 대상 타입을 초기화하는 문법입니다. 컴파일러는 대상 타입에 맞는 변환을 선택하지만, 그 결과가 항상 기존 코드보다 빠르거나 할당이 없다는 보장은 없습니다. 대상이 배열인지, List<T>인지, collection builder를 쓰는 타입인지에 따라 저장소 생성과 복사 비용이 달라집니다.
// 이전 방식 — 타입마다 다른 문법
int[] old1 = new int[] { 1, 2, 3 };
int[] old2 = new[] { 1, 2, 3 }; // 타입 추론 버전
List<int> old3 = new List<int> { 1, 2, 3 };
List<int> old4 = [1, 2, 3]; // ✅ C# 12 컬렉션 식
// 빈 컬렉션 — 이전 방식
int[] emptyArr = Array.Empty<int>();
List<int> emptyList = new List<int>();
// ✅ C# 12 — 통일된 표기
int[] emptyArr2 = [];
List<int> emptyList2 = [];spread 연산자 .. — 컬렉션 병합을 한 줄로
.. 연산자는 원본의 요소를 현재 위치에 펼쳐 새 대상 컬렉션에 넣습니다. 여러 컬렉션을 합치거나 고정 요소를 섞을 때 읽기 좋지만, 원본이 커도 결과 대상은 새로 만들어집니다. spread 원본은 열거될 수 있으므로 상태를 바꾸는 iterator나 한 번만 읽을 수 있는 입력은 먼저 물질화할지 판단합니다.
string[] animals = ["cat", "dog"];
string[] plants = ["rose", "tulip"];
string[] extra = ["sun"];
// 이전 방식
var merged1 = animals.Concat(plants).Concat(extra).ToArray();
var merged2 = new List<string>(animals);
merged2.AddRange(plants);
merged2.Add("sun");
// ✅ spread 연산자 — 순서와 내용이 한눈에 보임
string[] merged3 = [..animals, ..plants, ..extra];
// ["cat", "dog", "rose", "tulip", "sun"]
// 앞뒤에 고정 요소 추가도 자연스럽게
string[] all = ["start", ..animals, "middle", ..plants, "end"];대상 타입과 저장소 — 같은 문법이라도 결과가 같지 않다
컬렉션 식은 **대상 타입(target type)**을 기준으로 동작합니다. 변수 선언부의 타입, 메서드 파라미터 타입, 반환 타입 등을 보고 컴파일러가 내부적으로 어떤 컬렉션을 생성할지 결정합니다.
int[]→ 새 배열을 만듭니다.List<int>→ 새 List를 만들고 요소를 넣습니다. 알려진 길이에서는 용량을 미리 확보할 수 있습니다.Span<int>/ReadOnlySpan<int>→ 저장소를 가리키는 view가 됩니다. 컴파일러가 stack allocation을 선택할 수 있지만 보장하지 않습니다.IEnumerable<int>같은 인터페이스 → 요구한 인터페이스를 만족하는 구현이 만들어지며, 구체 타입과 identity에 의존하지 않습니다.- collection builder가 지정된 타입 → builder가 결과를 만들며, builder의 계약과 비용을 따릅니다.
// Span의 저장소는 컴파일러와 ref-safety 조건에 따라 선택된다.
void ProcessNumbers(Span<int> numbers)
{
foreach (var n in numbers) Console.WriteLine(n);
}
// 호출 측에서 컬렉션 식을 그대로 전달
ProcessNumbers([10, 20, 30]); // stack allocation을 가정하지 않는다.
// ImmutableArray와도 동작
ImmutableArray<string> tags = ["csharp", "dotnet", "linq"];var로는 타입 추론 불가 — 명시적 타입이 필요한 이유
컬렉션 식은 대상 타입이 명확해야 컴파일러가 구현을 선택할 수 있습니다. var는 타입을 추론할 대상이 없으므로 컴파일 오류가 발생합니다. 이는 컬렉션 식이 "리터럴"이 아니라 "대상 타입 의존 표현식"이기 때문입니다.
// ❌ var로는 타입 추론 불가 — 컴파일 오류
var items = [1, 2, 3]; // error CS9176: 컬렉션 식에 대상 형식이 없습니다
// ✅ 명시적 타입 선언
int[] items1 = [1, 2, 3];
List<int> items2 = [1, 2, 3];
// ✅ 타입이 추론되는 컨텍스트에서는 사용 가능
int[] result = GetNumbers();
static int[] GetNumbers() => [1, 2, 3]; // 반환 타입이 명확하므로 OK// ❌ 대상 타입이 없으면 컬렉션 식을 해석할 수 없다
// return [1, 2, 3];
// ✅ 반환 타입이 정해져 있으면 가능
static List<int> GetList() => [1, 2, 3];컬렉션 식 선택
| 상황 | 적합한 선택 |
|---|---|
| 배열, List, Span 초기화 | Type name = [elem1, elem2, ...] |
| 여러 컬렉션 병합 | [..a, ..b, extraElem] spread 연산자 |
| 빈 컬렉션 생성 | Type name = [] (Array.Empty<T>() 대체) |
| 메서드에서 컬렉션 반환 | return [elem1, elem2]; (반환 타입 명시) |
var와 함께 사용 | 불가 — 명시적 타입 필요 |
주의할 점
var로는 컬렉션 식을 사용할 수 없습니다. 컴파일러가 어떤 타입의 컬렉션을 만들어야 할지 알 수 없기 때문입니다. 반드시 대상 타입을 명시해야 합니다.
// ❌ 컴파일 오류
var nums = [1, 2, 3];
// ✅ 타입 명시
int[] nums1 = [1, 2, 3];
List<int> nums2 = [1, 2, 3];또한 spread 원본은 컬렉션 식이 열거하거나 복사할 수 있는 형태여야 합니다. 특히 Span<T> 대상에서는 수명·escape 규칙까지 만족해야 하므로, 임의의 IEnumerable<T>를 펼친다고 해서 할당이 없거나 항상 컴파일되는 것은 아닙니다. 대량·성능 민감 경로에서는 생성된 대상 타입과 할당을 측정해 확인하세요.
참고 링크
2 sources