C# - Collection 식을 지원하는 사용자 정의 타입을 CollectionBuilder 특성으로 성능 보완
컬렉션 식이 C# 12에 추가됐었는데요,
C# 12 - 컬렉션 식(Collection Expressions)
; https://www.sysnet.pe.kr/2/0/13415
위의 글에서도 설명했듯이, 사용자 정의 타입에 대한 컬렉션 식을 지원하는 것이 가능합니다.
using System.Collections;
internal class Program
{
static void Main(string[] args)
{
MyType list = [1, 2, 3, 4, 5, 6, 7, 8];
}
}
public class MyType : IEnumerable // 1. IEnumerable을 구현한 경우
{
List<int> _values { get; set; } = new List<int>();
public IEnumerator GetEnumerator()
{
return _values.GetEnumerator();
}
public void Add(int value) // 2. "Add" 이름을 갖는 메서드 추가 (확장 메서드로 구현해도 OK)
{
_values.Add(value);
}
}
그런데, 여기서 약간 아쉬운 점이 하나 있다면? "Add" 메서드를 이용한 초기화를 하다 보니 그다지 효율적인 코드가 나오지 않는다는 점입니다.
// 컬렉션 식을 사용한 경우, C# 컴파일러가 생성한 코드
MyType list = new MyType();
list.Add(1);
// ...[생략: 2, 3, 4, 5, 6, 7에 대해 동일하게 Add 호출]...
list.Add(8);
바로 저 문제를 해결하는 방법이
CollectionBuilder 특성을 통해 제공됩니다. 따라서 대충 다음과 같이 변경해 주면,
[CollectionBuilder(typeof(MyTypeCollectionBuilder), nameof(MyTypeCollectionBuilder.Create))]
public class MyType : IEnumerable<int>
{
List<int> _values = new List<int>();
public MyType(ReadOnlySpan<int> elems)
{
_values.AddRange(elems);
}
IEnumerator IEnumerable.GetEnumerator()
{
return _values.GetEnumerator();
}
public IEnumerator<int> GetEnumerator()
{
return ((IEnumerable<int>)_values).GetEnumerator();
}
}
public static class MyTypeCollectionBuilder
{
public static MyType Create(ReadOnlySpan<int> items) => new MyType(items);
}
C# 컴파일러는 이것을 인지하고, 컬렉션 식으로 초기화하는 코드를 다음과 같이 생성해 줍니다.
MyType list = MyTypeCollectionBuilder.Create(
RuntimeHelpers.CreateSpan<int>(fieldof(...1~8까지의 숫자를 포함하고 있는 바이너리 영역의 위치...).FieldHandle)
);
보는 바와 같이 컬렉션 식으로 개발자가 명시한 "[1, 2, 3, 4, 5, 6, 7, 8]" 배열 값을 컴파일 시에 미리 바이너리에 저장한 영역으로 대신하고, 그 저장소 공간을 ReadOnlySpan<int>로 받아 사용자가 원하는 코드(위의 경우 AddRange)로 바로 초기화하게 됩니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
참고로, CollectionBuilder로 지정하는 타입은 꼭 분리할 필요까진 없습니다. 즉, 위의 코드를 다음과 같이 하나의 타입에 정의해도 무방합니다.
[CollectionBuilder(typeof(MyType), nameof(Create))]
public class MyType : IEnumerable<int>
{
List<int> _values = new List<int>();
public MyType(ReadOnlySpan<int> elems)
{
_values.AddRange(elems);
}
IEnumerator IEnumerable.GetEnumerator()
{
return _values.GetEnumerator();
}
public IEnumerator<int> GetEnumerator()
{
return ((IEnumerable<int>)_values).GetEnumerator();
}
public static MyType Create(ReadOnlySpan<int> items) => new MyType(items);
}
관심 있으신 분들은 아래의 글도 읽어보시고. ^^
Adding support for collection expressions to your own types
; https://andrewlock.net/behind-the-scenes-of-collection-expressions-part-5-adding-support-for-collection-expressions-to-your-own-types/
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]