컬렉션 초기화 구문과 Index/Range 연산자는 컬렉션을 간결하게 만들고, 슬라이싱을 쉽게 해줍니다. 실전에서 바로 쓸 수 있는 문법과 주의점을 정리합니다.
1. 컬렉션 초기화 기본
컬렉션 초기화는 내부적으로 Add 호출을 나열하는 문법 설탕입니다. 가독성과 선언적 스타일에 유리합니다.
var numbers = new List<int> { 1, 2, 3, 4 };
var dict = new Dictionary<string, int>
{
{ "Kim", 30 }, // Add("Kim", 30)
{ "Lee", 28 }
};
// C# 6+ 인덱서 초기화: 존재하면 갱신, 없으면 추가
var ages = new Dictionary<string, int>
{
["Kim"] = 30,
["Lee"] = 28,
["Lee"] = 29 // Lee 키가 이미 있으면 값만 덮어씁니다
};사용자 타입도 Add 메서드만 제공하면 컬렉션 초기화를 지원합니다.
public class Team
{
private readonly List<string> _members = new();
public void Add(string name) => _members.Add(name);
public int Count => _members.Count;
}
var team = new Team { "Kim", "Lee", "Park" }; // Team.Add(...)가 호출됩니다2. 중첩 초기화와 최신 문법
딕셔너리 안에 리스트처럼 중첩 구조도 간단히 초기화할 수 있습니다. C# 9+에서는 대상 지정 new로 더 짧게 쓸 수 있습니다.
var squads = new Dictionary<string, List<string>>
{
["Red"] = new() { "A", "B" },
["Blue"] = new() { "C", "D", "E" }
};불변 컬렉션은 컬렉션 초기화가 보통 기대대로 동작하지 않습니다. 불변 컬렉션의 Add는 새 인스턴스를 반환하므로, 초기화 구문으로는 결과가 누적되지 않습니다. 팩터리나 Builder를 사용합니다.
using System.Collections.Immutable;
var good1 = ImmutableList.CreateRange(new[] { 1, 2, 3 });
var builder = ImmutableList.CreateBuilder<int> { 1, 2, 3 };
var good2 = builder.ToImmutable();3. Index/Range 한눈에
C# 8+에서는 ^와 ..로 끝에서부터 인덱싱과 슬라이싱이 가능합니다. 끝 인덱스는 항상 제외(exclusive)입니다.
var arr = new[] { 10, 20, 30, 40, 50 };
int last = arr[^1]; // 50, 끝에서 1번째
int secondLast = arr[^2]; // 40
int[] mid = arr[1..4]; // 20,30,40 (시작 포함, 끝 제외)
int[] head = arr[..3]; // 10,20,30 (처음부터)
int[] tail = arr[3..]; // 40,50 (끝까지)
int[] trim = arr[1..^1]; // 양끝 잘라내기: 20,30,40문자열에도 동일하게 적용됩니다.
string s = "abcdef";
char lastCh = s[^1]; // 'f'
string first3 = s[..3]; // "abc"
string middle = s[2..^2]; // "cd"Span/ReadOnlySpan에서 Range는 복사 없이 보기를 제공합니다. 배열/문자열의 Range 슬라이스는 새 배열/새 문자열을 만들어 복사합니다.
var data = Enumerable.Range(1, 10).ToArray();
Span<int> span = data; // 배열 -> Span (뷰)
Span<int> slice = span[3..^3]; // 4,5,6,7
slice[0] = 999; // data[3]도 999로 바뀝니다 (복사 없음)4. List에서의 지원 범위
Index/Range는 배열, 문자열, Span/ReadOnlySpan, Memory/ReadOnlyMemory에서 바로 지원합니다. List<T>는 .NET 8부터 Index/Range 인덱서를 지원합니다. 그 이전 버전에서는 대안을 사용합니다.
var list = new List<int> { 10, 20, 30, 40, 50 };
// .NET 8 미만 대안
int last = list[list.Count - 1];
var middle = list.GetRange(1, list.Count - 2); // 20,30,40
// 고급: Span으로 변환해 슬라이싱 (.NET 5+)
using System.Runtime.InteropServices;
Span<int> view = CollectionsMarshal.AsSpan(list);
var core = view[1..^1]; // 20,30,40 (복사 없는 뷰)
core[0] = 999; // list[1]이 999로 변경됨. 리스트 수정 시 Span은 무효화될 수 있으니 주의.5. 실전 팁
- 대량 초기화 시 List.Capacity를 먼저 설정하거나 AddRange를 고려하면 할당을 줄일 수 있습니다.
- Dictionary에서 키가 중복될 수 있으면 인덱서 초기화([key] = value)를, 중복이 논리적 오류면 Add 형태를 사용해 예외로 잡습니다.
- 큰 배열을 자를 때 배열 Range는 복사 비용이 듭니다. 성능이 중요하면 Span 슬라이싱을 고려합니다.
- 라이브러리 API에 Index/Range를 노출하려면 this[Index]/this[Range] 인덱서를 제공하거나, Slice(…), GetRange(…) 등 대안을 문서화합니다.
6. 종합 예제
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
public class Person { public string Name { get; set; } = ""; public int Age { get; set; } }
public static class Demo
{
public static void Run()
{
// 1) 컬렉션/객체 초기화
var people = new List<Person>
{
new() { Name = "Kim", Age = 30 },
new() { Name = "Lee", Age = 28 },
new() { Name = "Park", Age = 35 }
};
var byName = new Dictionary<string, Person>
{
["Kim"] = people[0],
["Lee"] = people[1],
["Park"] = people[2]
};
// 2) Index/Range로 조회
var ages = people.Select(p => p.Age).ToArray();
int oldest = ages[^1]; // 마지막 요소
int[] midAges = ages[1..^1]; // 양끝 제외
string id = "USR-2026-APR";
string suffix = id[^3..]; // "APR"
// 3) 성능형 슬라이스: List -> Span
var scores = new List<int> { 10, 20, 30, 40, 50, 60 };
Span<int> span = CollectionsMarshal.AsSpan(scores);
var lastThree = span[^3..]; // 40,50,60 (뷰)
lastThree[0] = 999; // scores[3] == 999
Console.WriteLine(oldest);
Console.WriteLine(string.Join(",", midAges));
Console.WriteLine(suffix);
Console.WriteLine(string.Join(",", scores));
}
}정리하면, 컬렉션 초기화로 선언을 간결하게, Index/Range로 접근과 슬라이싱을 직관적으로 만들 수 있습니다. 대상 런타임(.NET 8 이전/이후)과 복사/뷰 특성을 고려하여 적절히 선택하면 좋습니다.
'C#' 카테고리의 다른 글
| C# ref, out, in 매개변수 한 번에 정리 (0) | 2026.04.14 |
|---|---|
| C# static 키워드 완벽 이해 (1) | 2026.04.13 |
| C# 애트리뷰트 (Attribute) 정의와 활용 (0) | 2026.04.10 |
| C# StringBuilder로 문자열 성능 최적화 (0) | 2026.04.10 |
| C# Enum 활용법 (0) | 2026.04.10 |