昨日のオブジェクト初期化子に続き、今日はコレクション初期化子の話。
コレクション初期化子ってのは、例えば以下のようなやつのことです。
// この、{} の部分がコレクション初期化子。
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もそうで、Select
やWhere
など、所定のメソッドさえ持っていれば、クエリ式を使えます。
最近、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
の空実装しろと…