Object initializerの挙動が気になったので調べてみたメモ。
次のコードを考える。
this.Foo = new MyObj();
this.Foo.Prop = 1;
このとき、生成された MyObj
インスタンスが Foo
プロパティにセットされてから、MyObj
インスタンスの Prop
プロパティがセットされるまでの間に、ほかのスレッドから Foo
プロパティ経由で MyObj
インスタンスに触ることができる。MyObj.Prop
が設定されるまでは MyObj
インスタンスに触ってほしくないのだが、諸事情でコンストラクタの引数を増やしたくないという場面があった。もちろん直接プロパティにセットせずに変数に入れても良いのだけれども、なにかスマートな方法はないものかと思い、調べてみた。
結論から先に言うと、オブジェクト初期化子付のコンストラクタ呼び出しをすることで、この目標は達成できる。
this.Foo = new MyObj() { Prop = 1 };
いくつかの記事では「オブジェクト初期化子とコンストラクト後のプロパティセット(以下プロパティセット)は同義である」と記述されているが、以下の記事では生成されるILコードは異なると主張している。
重要な点を以下に引用する(カッコ内は著者注)。
MethodA(プロパティセット) と MethodB(オブジェクト初期化子) は同じ処理 と 思いきや 全然違いました。
Member プロパティの set である set_Member を実行するタイミングが、
MethodA では ”参照を value に代入した後” になっていますが、
MethodB では ”参照を value に代入する前” になっています。
この記事ではILを取り上げて挙動の違いを説明しているが、これがドキュメンテッドな挙動なのかどうかが気になったので、仕様書を読むことにした。
C#の言語仕様書はMSDNからダウンロードできる(恐ろしいことにWordドキュメントの形式で配布されている。C#6.0のspecはいつ公開されるのだろうか)。読むべき項目は「7.6.10.2 Object initializers」だ。
以下に当該箇所を抜粋する。
An instance of Point can be created and initialized as follows:
Pointインスタンスは次のように生成・初期化できる:Point a = new Point { X = 0, Y = 1 };
which has the same effect as
これは以下のコードと同じ効果を持つPoint __a = new Point(); __a.X = 0; __a.Y = 1; Point a = __a;
where
__a
is an otherwise invisible and inaccessible temporary variable.
ここで__a
は不可視でアクセスできない一時的な変数である。
つまり、Object initializer内に記述した初期化が行われたあとでオブジェクトへの参照が返ることが仕様書にも明記されている。
結論
インスタンス生成とメンバ初期化をアトミックに行いたいときはObject initializerを使う。