컬렉션 표현식 활용
이번 글에서는 C# 컬렉션 표현식(Collection Expression)을 통해 코드를 더 깔끔하고 효율적으로 만드는 방법을 살펴보겠습니다. 컬렉션 이니셜라이저(Collection Initializer)와 컬렉션 표현식, 스프레드(Spread) 문법 등을 다루며, 지원되는 컬렉션 타입도 알아보겠습니다. 이 기능들은 C# 개발자의 작업 효율을 높이고 코드 가독성(Readability)을 향상시키는 데 도움이 될 것입니다.
컬렉션 표현식의 도입
C# 12.0에서는 컬렉션을 일관성 있는 문법으로 초기화할 수 있는 컬렉션 표현식(Collection Expression)이 도입되었습니다. 기존의 이니셜라이저 방식과 같은 기능을 하면서도 더 간결한 코드를 작성할 수 있도록 돕습니다. 대괄호 [
와 ]
로 표현되는 컬렉션 표현식은 개발자가 코드를 더 쉽게 이해할 수 있게 합니다.
초기화 방법
다음은 정수 배열을 다양한 방법으로 초기화하는 예시입니다.
var numbers1 = new int[3] { 1, 2, 3 };
var numbers2 = new int[] { 1, 2, 3 };
var numbers3 = new[] { 1, 2, 3 };
int[] numbers4 = { 1, 2, 3 };
이 모든 방식은 같은 결과를 만들어냅니다. 새로운 컬렉션 표현식은 이니셜라이저(Initializer)의 중괄호 {}
대신 대괄호 []
를 사용합니다.
List<char> name = [ 'A', 'l', 'i', 'c', 'e' ];
컬렉션 표현식은 var
키워드와 함께 사용할 수 없으며, 반드시 컬렉션 타입을 명시해야 합니다. 다음과 같이 var
를 사용할 경우 오류가 발생합니다.
// 오류 CS9176: 컬렉션 표현식에 대한 대상 타입이 없습니다.
// Error CS9176: No target type for the collection expression.
var collection = [1, 2, 3];
일관된 문법으로 컬렉션을 표현하는 컬렉션 표현식은 가독성(Readability)과 유지보수성(Maintainability)을 향상시킵니다.
다양한 형태의 컬렉션 표현식
컬렉션이 비어있을 경우에도 표현식을 사용할 수 있습니다:
int[] emptyCollection = [];
컬렉션 표현식을 통해 기존의 new
키워드 방식보다 메모리를 더 효율적으로 활용할 수 있습니다. 인터페이스(Interface)를 사용해 컬렉션을 초기화할 때에도 컬렉션 표현식을 활용할 수 있습니다.
스프레드 기능
스프레드(Spread) 문법은 다른 컬렉션의 요소를 현재 컬렉션에 결합할 때 간결하게 표현하는 기능입니다.
int[] numbers1 = [1, 2, 3];
int[] numbers2 = [4, 5, 6];
int[] all = [..numbers2, 100, ..numbers1];
Console.WriteLine(string.Join(", ", all));
Console.WriteLine($"길이: {all.Length}");
// 출력(Output):
// 4, 5, 6, 100, 1, 2, 3
// 길이(Length): 7
스프레드 기능은 foreach
로 순회할 수 있는 모든 컬렉션에 적용할 수 있습니다.
지원되는 컬렉션 타입
컬렉션 표현식은 대부분의 컬렉션 타입을 지원합니다. BCL(Base Class Library)의 주요 컬렉션이 이미 업데이트되었고, 지원되지 않는 경우에도 속성(Property)이나 빌더 패턴(Builder Pattern)을 활용할 수 있습니다. 다만 현재 사전(Dictionary) 타입은 지원되지 않습니다.
리팩터링 예시
컬렉션 표현식은 다양한 상황에서 유용합니다.
- 널(Null)이 될 수 없는 컬렉션 타입 필드(Field), 속성(Property), 로컬 변수(Local Variable)를 초기화할 때
- 컬렉션 타입 매개변수(Parameter)를 받는 메서드(Method)에 인수로 전달할 때
다음은 컬렉션 이니셜라이저를 컬렉션 표현식으로 리팩터링한 예시입니다.
원본 소스는 다음과 같습니다.
public IEnumerable<Customer> Get()
{
return new List<Customer>
{
new Customer { Id = 1, Name = "김태영", City = "서울" },
new Customer { Id = 2, Name = "박용준", City = "인천" },
new Customer { Id = 3, Name = "한상훈", City = "경기" }
};
}
컬렉션 표현식으로 다음과 같이 줄일 수 있습니다.
public IEnumerable<Customer> Get()
{
return
[
new Customer { Id = 1, Name = "김태영", City = "서울" },
new Customer { Id = 2, Name = "박용준", City = "인천" },
new Customer { Id = 3, Name = "한상훈", City = "경기" }
];
}
식 본문을 사용하여 한 번 더 줄일 수 있습니다.
public IEnumerable<Customer> Get() => [
new Customer { Id = 1, Name = "김태영", City = "서울" },
new Customer { Id = 2, Name = "박용준", City = "인천" },
new Customer { Id = 3, Name = "한상훈", City = "경기" }
];
다음은 EventRegistry
개체를 초기화하는 예시입니다.
namespace EventManagement;
public sealed class EventRegistry
{
private readonly HashSet<Event> _events = [];
public Guid RegisterEvent(Event newEvent)
{
_ = _events.Add(newEvent);
return newEvent.Id;
}
public void RemoveEvent(Guid id)
{
_ = _events.RemoveWhere(x => x.Id == id);
}
}
public record Event(
bool IsActive,
string? ErrorDetails)
{
public Guid Id { get; } = Guid.NewGuid();
}
new HashSet<Event>()
를 []
로 간단하게 표현할 수 있습니다.
Span<T>
및 ReadOnlySpan<T>
지원
컬렉션 표현식은 Span<T>
와 ReadOnlySpan<T>
를 지원합니다.
Span<int> numbers = [1, 2, 3, 4, 5];
ReadOnlySpan<char> name = ['A', 'l', 'i', 'c', 'e'];
효율적인 메모리 사용을 통해 성능을 향상시킬 수 있습니다.
컬렉션 표현식 장점
컬렉션 표현식은 기존 이니셜라이저보다 효율적인 코드를 생성합니다.
List<int> someList = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
이니셜라이저는 Add
메서드를 사용하지만, 컬렉션 표현식은 AddRange
를 사용해 최적화된 코드를 생성합니다.