xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

インデックス初期化子

バージョン 5.0 現在の C# には「初期化子」と呼ばれる機能が 2 つあります。ひとつはオブジェクト初期化子で、インスタンス生成時にコンストラクタ引数を利用せずにフィールドやプロパティの値を設定するものです。もうひとつはコレクション初期化子で、これも同様にインスタンス生成時にコンストラクタ引数を利用せずにコレクションの初期要素を設定できます。

//--- オブジェクト初期化子
var person = new Person()
{
    Name = "xin9le",
    Age = 30,
};

//--- コレクション初期化子
var collection = new List<int>{ 1, 3, 5, 7 };

これらは以下の糖衣構文として提供されていますが、コンストラクタ引数での初期化ができない場合であってもたった 1 文で同様の初期化ができる、という点から大変重宝されてます。

//--- オブジェクト初期化子
var a = new Person();
a.Name = "xin9le";
a.Age = 30;
var person = a;

//--- コレクション初期化子
var b = new List<int>();
b.Add(1);
b.Add(3);
b.Add(5);
b.Add(7);
var list = b;

ちなみに、変数 a や変数 b のように一旦一時変数に受けているのは、プロパティの設定や Add メソッドの呼び出し時に例外が発生しても変数 person や変数 list が初期化されないようにするためです。

さてここで Dictionary 型のコレクション初期化子の書き方について見てみましょう。以下のようなコードで書くのですが、微妙に直観的でないというか、エレガントさに欠けるという難点がありました。そこに現れたインデックス初期化子

var numbers = new Dictionary<int, string>
{
    { 1, "one" },
    { 3, "three" },
    { 5, "five" },
};

インデックス初期化子

先のような問題解決するために、C# 6.0 からはインデックス初期化子という新たなシンタックスが追加されました。それにより以下のように記述できるようになります。見た目は微々たる違いですが、インデックスアクセスするものに対する直観的な記述をサポートすることで、書き易さと理解のし易さを提供しています。

var numbers = new Dictionary<int, string>
{
    [1] = "one",
    [3] = "three",
    [5] = "five",
};

コンパイル

先のインデックス初期化子の例を逆コンパイルして、どのように展開されるか確認してみます。結果は以下のようなインデクサによるアクセスに変換されます。コレクション初期化子は Add メソッドへの変換だったので、展開方法が異なることが分かります。

var a = new Dictionary<int, string>();
a[1] = "one";
a[3] = "three";
a[5] = "five";
var numbers = a;

インデックス初期化子を利用するためのコンパイラ要件はたったひとつで、set 可能なインデクサを実装していることだけです。つまり、この要件を満たせば独自型へも適用することができます。

独自型に対するインデックス初期化子

試しに独自型に対してインデックス初期化子を適用してみます。以下にそのサンプルを示しますが、インデクサを実装するだけなので非常に簡単ですね。

class MyBag
{
    public int this[string key]
    {
        get { throw new NotImplementedException(); } //--- 必須ではない
        set {}
    }
}

var bag = new MyBag
{
    ["hoge"] = 123,
    ["fuga"] = 456,
};