はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【C#】C#12から追加されたコレクション式を自身が定義した型も対応できるようにする方法

はじめに

C#12(.NET8)からコレクション式というものが導入されました。

int[] array = [1, 2, 3];
List<int> list = [1, 2, 3];
Span<int> span = [1, 2, 3];
ReadOnlySpan<int> ros = [1, 2, 3];
ImmutableArray<int> immutable = [1, 2, 3];

[1, 2,3]のようにして配列などのコレクションを初期化できる機能ですね。

コレクション式を使用して、共通のコレクション値を作成できます。 コレクション式は、評価時に、さまざまなコレクション型に割り当てることができる簡潔な構文です。 コレクション式には、[ と ] の括弧の間の一連の要素が含まれます。

コレクション式 (コレクション リテラル) - C# reference | Microsoft Learn

今回はコレクション式を自身が定義した型も対応できるようにする方法について紹介したいと思います。

概要

CollectionBuilderAttributeを用いることで実現できます。
learn.microsoft.com

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)]
#if SYSTEM_PRIVATE_CORELIB
    public
#else
    internal
#endif
    sealed class CollectionBuilderAttribute : Attribute
    {
        /// <summary>Initialize the attribute to refer to the <paramref name="methodName"/> method on the <paramref name="builderType"/> type.</summary>
        /// <param name="builderType">The type of the builder to use to construct the collection.</param>
        /// <param name="methodName">The name of the method on the builder to use to construct the collection.</param>
        /// <remarks>
        /// <paramref name="methodName"/> must refer to a static method that accepts a single parameter of
        /// type <see cref="ReadOnlySpan{T}"/> and returns an instance of the collection being built containing
        /// a copy of the data from that span.  In future releases of .NET, additional patterns may be supported.
        /// </remarks>
        public CollectionBuilderAttribute(Type builderType, string methodName)
        {
            BuilderType = builderType;
            MethodName = methodName;
        }

        /// <summary>Gets the type of the builder to use to construct the collection.</summary>
        public Type BuilderType { get; }

        /// <summary>Gets the name of the method on the builder to use to construct the collection.</summary>
        /// <remarks>This should match the metadata name of the target method. For example, this might be ".ctor" if targeting the type's constructor.</remarks>
        public string MethodName { get; }
    }
}


使い方としてはclassstructinterfaceに対して[CollectionBuilder]を付与し、第一引数builderTypeに対象の型を、第二引数methodNameにコレクションを構築するためのメソッドを渡します。

// Sampleという型に対して、構築するためにCreateメソッドを利用する
[CollectionBuilder(typeof(Sample), nameof(Create))]
public class Sample : IEnumerable<int>
{
    private readonly int[] _values;

    public Sample(IEnumerable<int> items)
    {
        _values = items.ToArray();
    }

    // IEnumerable<int>により実装したメソッド
    public IEnumerator<int> GetEnumerator() => _values.AsEnumerable().GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator();
    
    // CollectionBuilderのために追加したメソッド
    // staticメソッド + ReadOnlySpan<T>を引数に受け取る + 対象の型を返す
    public static Sample Create(ReadOnlySpan<int> items)
    {
        return new Sample(items.ToArray());
    }
}

実験

Sample sample = [1, 2, 3];
        
// Output: 1, 2, 3
Console.WriteLine(string.Join(", ", sample));