なぜ var d = new Dictionary<string, string> { { "a", "b" }, { "c", "d" } } と書けるのか ―― コレクション初期化子
執筆日時:
Dictionary ってその場で初期化できるんだね。
private Dictionary<string, string> AllowedFileType = new Dictionary<string, string>() { { "image/jpeg", "jpg" }, { "image/png" , "png" }, { "image/gif" , "gif" }, };こっちのほうがいいや。
簡単に書けるのはとっても素晴らしいのだけれど、イマイチこうやって書ける理由がわからなかったので調べてみました。
C# 3.0 のコレクション初期化子*1
とりあえず、基本となるコレクション初期化子(配列初期化子)の復習から。
List
は、次のように作成して初期化することができます。 var contacts = new List<Contact> { new Contact { Name = "Chris Smith", PhoneNumbers = { "206-555-0101", "425-882-8080" } }, new Contact { Name = "Bob Harris", PhoneNumbers = { "650-555-0199" } } };これは、以下と同じ効果を持ちます。
var contacts = new List<Contact>(); var __c1 = new Contact(); __c1.Name = "Chris Smith"; __c1.PhoneNumbers.Add("206-555-0101"); __c1.PhoneNumbers.Add("425-882-8080"); contacts.Add(__c1); var __c2 = new Contact(); __c2.Name = "Bob Harris"; __c2.PhoneNumbers.Add("650-555-0199"); contacts.Add(__c2);__c1 と __c2 は、このような方法を使用しなければ隠蔽されアクセスできないテンポラリ変数です。
つまり、
var x = List<string>() { "a", "b" };
は、
var x = List<string>(); x.Add("a"); x.Add("b");
とほぼ同じ。ただし、配列初期化子を利用して初期化できるクラスは、 IEnumerable が実装されていること、メンバーとして Add() が実装されていることの二つが条件となります。逆に言えば、それさえ満たしていれば内容は問われません(後述)。
足りない条件
さて、 List の場合はだいたいわかりました。けれど、 Dictionary の場合はそれではまだ説明が足りないと思います。
@aetos382 @Grabacr07 そうなんですよね。たとえば IEnumerable 実装してて、Add(arg1, arg2) ってシグネチャがあってればOK とか、そういうルールあるのかなって
— だるやなぎ に天使が舞い降りた! (@daruyanagi) 2012年8月22日
以下のコードで言えば、なぜ { "a", "b" } なんて書けるのか、ちょっとわかりません。
var d = new Dictionary<string, string> { { "a", "b" }, // <-- この {} って何さ! { "c", "d" } }
すると、こんな助言をいただくなど。
@okazuki なるほどー パラメーターはいくつでもいいのか
— だるやなぎ に天使が舞い降りた! (@daruyanagi) 2012年8月22日
そういうわけで少し試してみました。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace ConsoleApplication1 { using System.Collections; using System.Diagnostics;
class SampleClass : IEnumerable<string> { public IEnumerator<string> GetEnumerator() { throw new NotImplementedException(); }
IEnumerator GetEnumerator() { throw new NotImplementedException(); }
public void Add(string arg1, string arg2) { Debug.WriteLine("{0}, {1}", arg1, arg2); } }
class Program { static void Main(string[] args) { var a = new List<string>() { "a", "b" }; a.ForEach(() => Debug.WriteLine());
var b = new List<string>() { { "c" }, { "d" } }; b.ForEach(() => Debug.WriteLine());
// var c = { 1 };
var d = new SampleClass() { { "o", "p" }, { "q", "r" } };
Console.WriteLine("何かキーを押してください。"); Console.ReadLine(); } } }
少しずつカラクリが見えてきました。
{ x } で Add(x) が呼ばれる
var b = new List<string>() { { "c" }, { "d" } };
全然知らなかったのだけれど、こんな書き方もいけるのですね。つまり、
var a = new List<string>() { "a", "b" };
において、 "a" は { "a" } の省略記法と見なせそう。
var c = { 1 };
さすがにこれはダメですね。 { } ではなく ( ) ならばそのままコンパイルできるのですが。
ゴメンナサイ。
中身はどうでもいい。とりあえず IEnumerable を実装して Add() 書いとけ
SampleClass は IEnumerable
class SampleClass : IEnumerable<string> { public IEnumerator<string> GetEnumerator() { throw new NotImplementedException(); }IEnumerator GetEnumerator() { throw new NotImplementedException(); }
public void Add(string arg1, string arg2) { Debug.WriteLine("{0}, {1}", arg1, arg2); } }
それでも、これが動くんですね。
var d = new SampleClass() { { "o", "p" }, { "q", "r" } };
ただ単に Add() してるのを簡易記法で隠蔽しているだけだとわかれば、何も難しくありません。
var d = new SampleClass(); d.Add("o", "p"); // <-- 実際にはコレクションに値を追加していない! d.Add("q", "r");
こういうのを糖衣構文(シンタックスシュガー)というのですね。ちなみに、引数の数を間違えると静的にチェックされ、さすがにコンパイルエラーになります。
var d = new SampleClass() { { "o", "p" }, { "q", "r", "s" } // <-- アウチ! };
ちょっと興味深かったのでまとめてみました。
*1:.NET Framework 3.5