昨日のオブジェクト初期化子に続き、今日はコレクション初期化子の話。

コレクション初期化子ってのは、例えば以下のようなやつのことです。

// この、{} の部分がコレクション初期化子。
var x = new List<int> { 1, 2, 3, 4, 5 };

このコレクション初期化を使える条件は、Add メソッドを持っていて、かつ、 IEnumerable を実装していることです。

最低限の実装をしてみると、以下のような感じ。

class MyList : IEnumerable
{
    List<int> _list = new List<int>();
    public void Add(int value) => _list.Add(value);
    public IEnumerator GetEnumerator() => _list.GetEnumerator();
}

static void ListSample()
{
    var x = new MyList { 1, 2, 3, 4, 5 };

    foreach (var item in x)
        Console.WriteLine(item);
}

この、コレクション初期化子は以下のように展開されます。

var x = new MyList();
x.Add(1);
x.Add(2);
x.Add(3);
x.Add(4);
x.Add(5);

ここで生じる疑問があります: IEnumerable の実装、要るの?

依存は避けれるなら避けるべきもの

だって、Addメソッドしか使ってなくない?IEnumerableは何にも使ってないよね?

だいたい、C#の文法がIEnumerableに依存しちゃうの? 例えば、foreachであればGetEnumeartorメソッドさえ持っていれば、別にIEnumerableインターフェイスを実装していない型であっても使えます。 LINQもそうで、SelectWhereなど、所定のメソッドさえ持っていれば、クエリ式を使えます。

最近、Build InsiderでTask-likeの話とかも書きましたけど、 言語の文法が何かの型に依存するというのはリスクを持ちます。 可能なら避けるべきものです。

で、コレクション初期化子、IEnumerable 要るの?

たぶん、誤用の防止

まあ、問題になるとすると以下のような例ですかね。

struct Adder
{
    public int Add(int x, int y) => x + y;
}

static void AdderSample()
{
    // こういう誤用を防ぎたかったのかなという気はする
    var x = new Adder
    {
        { 2, 1 },
        { 3, 4 },
        { 5, 9 },
    };
}

Addメソッドだけを条件にしてしまうと、こういうコードが書けてしまう。 で、このAddの呼ばれ方だと、何の役にも立たないわけです。 Adderの内部状態を変えたいわけじゃなてく、単なるオペレーターなわけでして。

もちろん、IEnumerableの実装を義務付けたところで、あえて濫用することはできます。 例えば、以下のような書き方なら現在の仕様でもできます。

class Accumulator : IEnumerable
{
    public int Sum { get; set; }
    public int Add(int value) => Sum += value;

    // 空実装してしまえば、コレクション初期化子の乱用可能
    public IEnumerator GetEnumerator() => throw new NotSupportedException();
}

static void AccumulatorSample()
{
    // コレクションでもないんでもないけど、コレクション初期化子を使える
    var x = new Accumulator { 1, 2, 3, 4, 5 };
    Console.WriteLine(x.Sum); // 15
}

とりあえず空実装。

まあ、意図的にやってるので大して問題にはならないんですが。 Adderみたいなのが意図せずコレクション初期化子で使われるのだけは防止したかったんですかね… そのためにGetEnumeratorの空実装しろと…