Quick Syntax
new T { ... }는 object initializer 또는 collection initializer를 뜻할 수 있습니다. 컬렉션 요소를 { value }로 넣으면 접근 가능한 Add를 호출하고, [key] = value는 set 가능한 인덱서에 대입합니다. C# 12의 [...]은 별도 대상 타입 의존 컬렉션 식이므로 기존 initializer와 변환 규칙이 다릅니다.
// object initializer
var player = new Player { Name = "Mina", Level = 1 };
// 컬렉션 초기화식
var numbers = new List<int> { 1, 2, 3, 4 };
var scores = new Dictionary<string, int>
{
["Mina"] = 1200,
["Jin"] = 980,
};
// C# 9+ target-typed new
List<int> primes = new() { 2, 3, 5, 7 };
// C# 12+ 컬렉션 식
int[] arr = [1, 2, 3];
List<int> list = [1, 2, 3];문법
어떤 초기화 문법을 먼저 고르면 되나
| 상황 | 먼저 떠올릴 것 |
|---|---|
| 모든 버전에서 가장 무난 | new T { ... } |
| 왼쪽 타입이 분명함 | new() { ... } |
| C# 12 이상, 짧은 통일 문법 | [...] |
| 기존 컬렉션 합치기 | [..a, ..b] |
Add 초기화와 인덱서 초기화는 다르다
컬렉션 initializer에서 요소를 나열하는 형태는 문법 설탕(syntactic sugar) 으로 접근 가능한 Add 인스턴스 또는 확장 메서드를 호출합니다. 반면 dictionary의 [key] = value는 인덱서 setter에 대입합니다. 둘은 중복 키의 결과가 다르므로 편의 문법으로 서로 바꾸지 않습니다.
// 요소 initializer는 Add 호출과 같은 의미
var numbers = new List<int> { 1, 2, 3 };
// 컴파일러가 생성하는 코드
var temp = new List<int>();
temp.Add(1);
temp.Add(2);
temp.Add(3);
var numbers = temp;// { key, value }는 Add(key, value) 호출: 중복 키면 ArgumentException
var viaAdd = new Dictionary<string, int>
{
{ "Mina", 1200 },
// { "Mina", 1300 }, // 실행하면 중복 키 예외
};
// [key] = value는 인덱서 setter: 같은 키를 덮어쓴다.
var viaIndexer = new Dictionary<string, int>
{
["Mina"] = 1200,
["Mina"] = 1300,
};object initializer의 Property = value는 Add가 아니라 생성 뒤 접근 가능한 필드·프로퍼티·인덱서에 대입합니다. 따라서 어떤 syntax인지에 따라 대상 타입이 제공해야 할 API도 달라집니다.
target-typed new() (C# 9+)
타입을 왼쪽에서 추론할 수 있으면 오른쪽의 타입 이름을 생략할 수 있습니다. 제네릭 타입이 길 때 특히 유용합니다.
Dictionary<string, List<int>> map = new()
{
["a"] = new() { 1, 2, 3 },
["b"] = new() { 4, 5, 6 },
};컬렉션 식 (C# 12+)
[...] 문법은 배열, List<T>, ImmutableArray<T> 등 다양한 컬렉션 타입에 대상 타입을 기준으로 변환되는 표현식을 제공합니다. spread 연산자(..) 로 원본 요소를 새 대상에 펼쳐 넣을 수 있지만, 기존 collection initializer의 Add 호출과 같은 변환을 보장하지는 않습니다.
int[] first = [1, 2, 3];
int[] second = [4, 5, 6];
int[] all = [..first, ..second, 7, 8]; // [1,2,3,4,5,6,7,8]
// ReadOnlySpan<T>에도 사용 가능 — stack allocation 여부는 보장하지 않음
ReadOnlySpan<int> span = [1, 2, 3];// ❌ target type이 없으면 컬렉션 식 타입을 추론할 수 없음
// var items = [1, 2, 3];
// ✅ 왼쪽에서 타입을 알려 줘야 함
int[] items = [1, 2, 3];
List<int> list = [1, 2, 3];초기화 문법 선택
| 문법 | C# 버전 | 특징 |
|---|---|---|
new T { ... } | 초기 | 가장 범용, 모든 버전 |
new() { ... } | 9+ | 타입 이름 생략 가능 |
[...] | 12+ | 통일된 문법, 스프레드 지원 |
..spread | 12+ | 기존 컬렉션을 인라인으로 펼침 |
주의할 점
초기화 블록에 값이 너무 많아지면 코드보다 데이터 파일(JSON, CSV 등)로 관리하는 편이 낫습니다. 코드에 수십 개의 초기값을 나열하면 가독성이 떨어지고 변경 이력 추적도 어려워집니다.
컬렉션 식 [...]은 대상 타입이 있어야 합니다. var items = [1, 2, 3];처럼 타입 정보가 없으면 컴파일 오류가 납니다. Span<T> 대상은 stack allocation을 사용할 수 있지만 대상의 수명·컴파일러·문맥에 따라 heap allocation이 될 수 있으므로, 성능 계약으로 가정하지 않습니다.
참고 링크
2 sources