C# 12 で導入されたコレクション式は、CollectionBuilderAttribute
を用いることで、独自型にも導入できる。
公式ドキュメント
・コレクション式 (コレクション リテラル) - C# | Microsoft Learn
・CollectionBuilderAttribute クラス (System.Runtime.CompilerServices) | Microsoft Learn
まずは普通に使ってみる。
PathCollection という(float, float)
のコレクションを考える。
(※ IEnumerable<T>
継承は必須ではないという指摘をいただき、修正しました 1)
// ↓ 必要:CollectionBuilder 属性を付け、ビルダーの型とメソッド名を指定 [CollectionBuilder(typeof(PathCollectionBuilder), nameof(PathCollectionBuilder.Create))] public class PathCollection : IEnumerable<(float, float)> // <- IEnumerable<T>の継承は必須ではないよう { private readonly (float, float)[] paths; public PathCollection(ReadOnlySpan<(float, float)> paths) { this.paths = new (float, float)[paths.Length]; paths.CopyTo(this.paths); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public IEnumerator<(float, float)> GetEnumerator() // <- 必要:列挙可能 { foreach (var path in paths) yield return path; } } public static class PathCollectionBuilder { // ↓ 必要:ビルダーのメソッドは ReadOnlySpan を受け、static public static PathCollection Create(ReadOnlySpan<(float, float)> span) => new(span); }
これでコレクション式が導入できた。
PathCollection paths = [(0f, 0f), (0.1f, 0.1f), (1f, 1f)];
なお、ビルダーメソッドはstatic
である必要があるがビルダー型がstatic
な必要はなく、また、ビルダー型が対象の型と別である必要はない。
他に、コレクション式を使うだけならGetEnumerator
は not implemented でも問題ない。
それらをふまえ以下に、コレクションではない矩形を表す構造体で糖衣的にコレクション式を使ってみる、といった例2。
[CollectionBuilder(typeof(Rect), nameof(Rect.Create))] public struct Rect { public float X; public float Y; public float Width; public float Height; public IEnumerator<float> GetEnumerator() => throw new NotImplementedException(); public static Rect Create(ReadOnlySpan<float> span) => new() { X = span[0], Y = span[1], Width = span[2], Height = span[3] }; }
private string PrintRectCore(Rect rect) => $"({rect.X}, {rect.Y}) {rect.Width}x{rect.Height}"; public void PrintRect() { Console.WriteLine(PrintRectCore([0f, 0f, 24f, 12f])); }
導入自体は手軽。とはいえ、本来の用途外に使うのは色々と厳しそう。
なお、要素型はジェネリックでも可。
例:Source Browser(リンク先:ImmutableList<T>
)
一方で、複数の要素型を指定することはできなかった。
[CollectionBuilder(typeof(RGB), nameof(RGB.Create))] public struct RGB : IEnumerable<float>, IEnumerable<byte> { public float R; public float G; public float B; IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); public IEnumerator<float> GetEnumerator() => throw new NotImplementedException(); // 「明示的なインターフェースの実装」で要素型を複数用意できるけれど―― IEnumerator<byte> IEnumerable<byte>.GetEnumerator() => throw new NotImplementedException(); public static RGB Create(ReadOnlySpan<float> span) => new() { R = span[0], G = span[1], B = span[2] }; // コレクション式で呼び分けてはくれず、こちらは呼ばれない public static RGB Create(ReadOnlySpan<byte> span) => new() { R = span[0] / 255f, G = span[1] / 255f, B = span[2] / 255f }; }
RGB rByte = [(byte)64, (byte)64, (byte)64]; ReadOnlySpan<byte> bytes = [64, 64, 64]; RGB rByte2 = RGB.Create(bytes); Console.WriteLine($"({rByte.R}, {rByte.G}, {rByte.B})"); Console.WriteLine($"({rByte2.R}, {rByte2.G}, {rByte2.B})"); // (64, 64, 64) // (0.2509804, 0.2509804, 0.2509804) // ↑ コレクション式で byte を渡しているが、暗黙変換で Create(ReadOnlySpan<float>) が呼ばれている(上段)。下段が byte のオーバーロードの出力
基本的に、通常想定された使い方での利用となりそう。(当たり前)