none
(C#)オブジェクト初期化子について RRS feed

  • 質問

  • C#の初歩の勉強中の者です。初歩の質問で申し訳ありません。

    下記のコードの class A  の List1 の定義に set アクセサが無いのに ① の一文で list1 が初期化されるのはどうしてだか分りません。 

    宜しくお願いします。

    ====(コード始まり)=======================================

    using System;
    using System.Collections.Generic;

    class A
    {
        private List<int> list1 = new List<int>();
        public List<int> List1
        {
            get { return list1; }

       //set アクセサの記述がありませんが、下の①の実行で、list1 が初期化されるのはどうしてでしょうか?
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            var a = new A()
            {
                List1 = { 2, 3, 5 }  //①
            };

            a.List1.ForEach((i) => { Console.WriteLine("{0}", i); });
            //出力:
            //2
            //3
            //5
        }
    }

    ====(コード終り)=======================================

    2016年6月26日 16:07

回答

  • var a = new A()
    {
        List1 = new List<int> { 2, 3, 5 }
    };

    であるならばコレクションAのオブジェクト初期化子でList1プロパティにList<int>インスタンスをセットするという意味になりますが、今回のはコレクション初期化子となります。

    コレクション初期化子の説明は

    コレクション初期化子を使用すると、IEnumerable を実装するコレクション クラスを初期化するときに 1 つ以上の要素の初期化子を指定できます。 要素の初期化子は、単純な値、式またはオブジェクト初期化子です。 コレクション初期化子を使用すると、ソース コード内でクラスの Add メソッドの呼び出しを複数回指定する必要がなくなります。コンパイラによって呼び出しが追加されるためです。

    とあります。
    これはIEnumerableインターフェースが実装されているならば、コンパイラは初期化のためにAddメソッドを呼び出すコードを追加するという意味です。
    ですからクラスAのオブジェクト初期化子内で、List1プロパティにsetするのではなく、List1プロパティで得られるIEnumerableを実装しているオブジェクトに対してAdd()を行うということになります。

    以下のコードを実行すればAddメソッドが呼ばれることがわかると思います。

    namespace ConsoleApplication1
    {
        using System;
        using System.Collections.Generic;
    
        class Program
        {
            static void Main(string[] args)
            {
                var a = new A()
                {
                    List1 = { 2, 3, 5 }
                };
    
                //以下のAdd()に展開されるのと同等
                //A a=new A();
                //a.List1.Add(2);
                //a.List1.Add(3);
                //a.List1.Add(5);
            }
        }
        class A
        {
            private X x = new X();
            public X List1
            {
                get { return x; }
            }
    
        }
    
        class X : System.Collections.IEnumerable
        {
            List<int> list = new List<int>();
            public void Add(int value)
            {
                list.Add(value);
                Console.WriteLine("X.Add({0})", value);
            }
    
            #region IEnumerable メンバー
            public System.Collections.IEnumerator GetEnumerator()
            {
                throw new NotImplementedException();
            }
            #endregion
        }
    
    }

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2016年6月26日 18:03

すべての返信

  • var a = new A()
    {
        List1 = new List<int> { 2, 3, 5 }
    };

    であるならばコレクションAのオブジェクト初期化子でList1プロパティにList<int>インスタンスをセットするという意味になりますが、今回のはコレクション初期化子となります。

    コレクション初期化子の説明は

    コレクション初期化子を使用すると、IEnumerable を実装するコレクション クラスを初期化するときに 1 つ以上の要素の初期化子を指定できます。 要素の初期化子は、単純な値、式またはオブジェクト初期化子です。 コレクション初期化子を使用すると、ソース コード内でクラスの Add メソッドの呼び出しを複数回指定する必要がなくなります。コンパイラによって呼び出しが追加されるためです。

    とあります。
    これはIEnumerableインターフェースが実装されているならば、コンパイラは初期化のためにAddメソッドを呼び出すコードを追加するという意味です。
    ですからクラスAのオブジェクト初期化子内で、List1プロパティにsetするのではなく、List1プロパティで得られるIEnumerableを実装しているオブジェクトに対してAdd()を行うということになります。

    以下のコードを実行すればAddメソッドが呼ばれることがわかると思います。

    namespace ConsoleApplication1
    {
        using System;
        using System.Collections.Generic;
    
        class Program
        {
            static void Main(string[] args)
            {
                var a = new A()
                {
                    List1 = { 2, 3, 5 }
                };
    
                //以下のAdd()に展開されるのと同等
                //A a=new A();
                //a.List1.Add(2);
                //a.List1.Add(3);
                //a.List1.Add(5);
            }
        }
        class A
        {
            private X x = new X();
            public X List1
            {
                get { return x; }
            }
    
        }
    
        class X : System.Collections.IEnumerable
        {
            List<int> list = new List<int>();
            public void Add(int value)
            {
                list.Add(value);
                Console.WriteLine("X.Add({0})", value);
            }
    
            #region IEnumerable メンバー
            public System.Collections.IEnumerator GetEnumerator()
            {
                throw new NotImplementedException();
            }
            #endregion
        }
    
    }

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2016年6月26日 18:03
  • 丁寧な回答ありがとうございます。

               //以下のAdd()に展開されるのと同等
               
    //A a=new A();
               
    //a.List1.Add(2);
               
    //a.List1.Add(3);
               
    //a.List1.Add(5);
    は理解できました。

    でも、このままではList1が初期化されるのは分りますが、private X x の x への入力はどこで起こるのですか?

    くどくてすみません。

    → a.List1.Add(2); →List1.getがよばれて a.list1.Add(2); 

    → a.List1.Add(3; →List1.getがよばれて a.list1.Add(3); 

    → a.List1.Add(5); →List1.getがよばれて a.list1.Add(5); 

    ということでしょうか?


    2016年6月26日 21:27
  • Aのフィールド初期化子で初期化されてますよね。

    A a = new A() { List = { ... } };の箇所ではList1への代入は発生しませんし、フィールド初期化子やAコンストラクタ内部でxを初期化していない場合、コレクション初期化子が実行された時点でNullReferenceExceptionが発生します。


    • 編集済み Hongliang 2016年6月26日 21:33
    2016年6月26日 21:32
  • 回答ありがとうございます。

    初期化ではなく、List1 = { 2, 3, 5 } がどのようにして list1 = { 2, 3, 5 } になるかということです。

    2016年6月26日 21:46
  • t1が初期化されるのは分りますが、private X x の x への入力はどこで起こるのですか?

    くどくてすみません。

    フィールドxの初期化はコンストラクタの実行前に初期化が行われます。
    その後コンストラクタが実行され、最後にオブジェクト初期化子内の初期化が実行されます。

    → a.List1.Add(2); →List1.getがよばれて a.list1.Add(2);
    → a.List1.Add(3; →List1.getがよばれて a.list1.Add(3);
    → a.List1.Add(5); →List1.getがよばれて a.list1.Add(5); 

    ということでしょうか?

    List1プロパティのgetで内部のフィールドを読み出して、そのインスタンスのメソッドAddを実行です。
    これは初期化とは関係なく普通のプロパティに対して実行されることと同じです。(そういう風にコードが追加されるので)

    以下は言語仕様書からの抜粋です

    コレクション初期化子が適用されるコレクション オブジェクトは、System.Collections.IEnumerable を実装する型である必要があります。この型でない場合は、コンパイル エラーが発生します。順に指定された要素ごとに、コレクション初期化子によりターゲット オブジェクトで要素初期化子の式リストを引数リストとして Add メソッドが呼び出され、各呼び出しに標準のオーバーロードの解決が適用されます。したがって、コレクション オブジェクトには、各要素初期化子に対する適切な Add メソッドが含まれている必要があります。
    次のクラスは、名前、および電話番号のリストを含む連絡先を表しています。
    public class Contact
    {
    	string name;
    	List<string> phoneNumbers = new List<string>();
    	public string Name { get { return name; } set { name = value; } }
    	public List<string> PhoneNumbers { get { return phoneNumbers; } }
    }
    List<Contact> は、次のようにして作成し初期化できます。
    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 __clist = new List<Contact>();
    Contact __c1 = new Contact();
    __c1.Name = "Chris Smith";
    __c1.PhoneNumbers.Add("206-555-0101");
    __c1.PhoneNumbers.Add("425-882-8080");
    __clist.Add(__c1);
    Contact __c2 = new Contact();
    __c2.Name = "Bob Harris";
    __c2.PhoneNumbers.Add("650-555-0199");
    __clist.Add(__c2);
    var contacts = __clist;
    ここで、__clist、__c1、および __c2 は、参照もアクセスもできない一時変数です。
    
    仕様書でこのように定義されているので、これ以上詳しく理解したい場合はアセンブリを逆アセンブリしてILを読む必要があります。



    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2016年6月26日 23:11
  • ”List1プロパティのgetで内部のフィールドを読み出して、そのインスタンスのメソッドAddを実行です。”

    ということですね。度々ありがとうございます。

    ILに挑戦してみます。


    2016年6月26日 23:28
  • ILの前に言語仕様を読まれてはどうでしょうか? 分量は多いですが、日本語に翻訳されているためそこそこ読みやすいです。

    Visual Studioのインストールディレクトリ下にWordファイルが置かれています(C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC#\Specificationsなど)。

    2016年6月27日 0:59
  • 指摘のフォルダーにファイル見つけました。ありがとうございます。

    かなり読み応えがありますね。

    情報ありがとうございます。

    2016年6月27日 7:26