手続き型音楽の日常

関数型音楽に乗り換えたい

コレクション初期化子とオブジェクト初期化子はどちらが優先されるのか

ちょっと気になったのでメモ。

結論

オブジェクト初期化子のほうが優先される

実験

No.1

まず、普通にオブジェクト初期化子を書く。

using System.Collections.Generic;

namespace ClassInheritedListSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Hoge hoge = new Hoge { A = 1, C = 2 };
        }
    }

    class Hoge
    {
        public int A { get; set; }
        public string B { get; set; }
        public int C { get; set; }
    }
}

オブジェクト初期化子に違いない。

No.2

次に、 List<int> を継承してみる。

using System.Collections.Generic;

namespace ClassInheritedListSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Hoge hoge = new Hoge { A = 1, C = 2 };
        }
    }

    class Hoge : List<int>
    {
        public int A { get; set; }
        public string B { get; set; }
        public int C { get; set; }
    }
}

オブジェクト初期化子のままだ。

f:id:yuzutan_hnk:20170923022100p:plain:w600

No.3

次に、 Program クラス側に同様のプロパティを定義する。

using System.Collections.Generic;

namespace ClassInheritedListSample
{
    class Program
    {
        public static int A { get; set; }
        public static string B { get; set; }
        public static int C { get; set; }

        static void Main(string[] args)
        {
            Hoge hoge = new Hoge { A = 1, C = 2 };
        }
    }

    class Hoge : List<int>
    {
        public int A { get; set; }
        public string B { get; set; }
        public int C { get; set; }
    }
}

オブジェクト初期化子のままだ。

f:id:yuzutan_hnk:20170923022359p:plain:w600

No.4

Program に用意したプロパティの型を変えてみる。

using System.Collections.Generic;

namespace ClassInheritedListSample
{
    class Program
    {
        public static int A { get; set; }
        public static int B { get; set; }
        public static int C { get; set; }

        static void Main(string[] args)
        {
            Hoge hoge = new Hoge { A = 1, B = 2 };
        }
    }

    class Hoge : List<int>
    {
        public int A { get; set; }
        public string B { get; set; }
        public int C { get; set; }
    }
}

これもオブジェクト初期化子のまま。 Hoge.B の型が int じゃないと怒られる。

f:id:yuzutan_hnk:20170923022737p:plain:w600

No.5

かっこで括る。

using System.Collections.Generic;

namespace ClassInheritedListSample
{
    class Program
    {
        public static int A { get; set; }
        public static int B { get; set; }
        public static int C { get; set; }

        static void Main(string[] args)
        {
            Hoge hoge = new Hoge { (A = 1), (B = 2) };
        }
    }

    class Hoge : List<int>
    {
        public int A { get; set; }
        public string B { get; set; }
        public int C { get; set; }
    }
}

やっとコレクション初期化子になった。

f:id:yuzutan_hnk:20170923022932p:plain:w600

どうしてこうなるのか

C# 5.0 の言語仕様 7.6.10.3 項を見ると、書いてあります。

コレクション初期化子は、"{“ トークンと ”}“ トークン内のコンマで区切った要素初期化子のシーケンスから構成されます。各要素初期化子は、初期化対象のコレクション オブジェクトに追加する要素を指定し、”{“ トークンと ”}“ トークン内のコンマで区切った式のリストから構成されます。単一式の要素初期化子は、 かっこなしで作成することができますが、メンバー初期化子とのあいまい性を防ぐため、代入式にすることはできません 。non-assignment-expression の生成は 7.18 で定義されています。

ここでいうメンバー初期化子とは、たぶんオブジェクト初期化子で使用される A = B の形式のものだと思われます。明確な定義が見つけられないのですが、オブジェクト初期化子の説明にもメンバー初期化子という言葉が使われています。

オブジェクト初期化子では、メンバー初期化子の形式は次のようにあります。

オブジェクト初期化子は、"{“ トークンと ”}“ トークン内のコンマで区切った一連のメンバー初期化子から構成されます。各メンバー初期化子では、 初期化対象のオブジェクトのアクセス可能なフィールドまたはプロパティを指定し、その後に等号と式、またはオブジェクト初期化子かコレクション初期化子を指定する 必要があります。オブジェクト初期化子に、同じフィールドまたはプロパティに対する複数のメンバー初期化子を含めるのは誤りです。オブジェクト初期化子は、初期化対象の新しく作成したオブジェクトを参照することはできません。

上記二つから考えるに、コンパイラ的には

  • 名称 = から始まる要素が1つでもあれば、オブジェクト初期化子
  • のみで構成される要素しかなければ、コレクション初期化子

と分類している気がします。