usingブロックのオブジェクト初期化子

オブジェクトの初期化子(オブジェクト初期化子)はC#言語の便利な機能であり、作成中にオブジェクトの必要なプロパティを初期化できます。 構文的にこの「機能」は、コンストラクターを介してパラメーターを渡すことでオブジェクトを初期化することに非常に近いため、多くの開発者はOOPの原則(特に不変式の概念)を徹底的に使い始め、可能な限り使用します。



しかし、たとえホリバーや曖昧な用語に目を向けなくても、小さな例を見て、それが問題につながるかどうかを考えましょう。



// position     -  long position = -1; using (var file = new FileStream("d:\\1.txt", FileMode.Append) { //   ,     //   ! Position = position }) { //  -   }
      
      







このフラグメントでは、 usingディレクティブ内でリソース(ファイル)が作成され、そのプロパティの1つ( Position )がオブジェクト初期化子を使用して設定されます。 このコードで最も重要なことはこのプロパティのセッターが例外をスローできることです。



.NETのC ++言語とは異なり、例外の安全性に関する問題に遭遇することはほとんどありませんが、これは例外の観点からコードが安全でないまれなケースの1つです。



これを理解するために、オブジェクト初期化子がコンパイラによってどのように実装されるかを見てみましょう。



 class Person { public string Name { get; set; } public int Age { get; set; } } // ... var person = new Person {Name = "John", Age = 42};
      
      







一見すると、オブジェクトの初期化子は、その後のプロパティの変更を伴うコンストラクターの呼び出しに過ぎないように思えるかもしれません。 そして、実際には、少し明確になっているだけです。



 var tmp = new Person(); tmp.Name = "Jonh"; tmp.Age = 42; var person = tmp;
      
      







一時変数は、初期化の「アトミック性」に必要な条件であり、サードパーティコード(別のスレッドなどから)が中間状態のオブジェクトへの参照を取得できません。 さらに、プロパティのセッターから例外をスローすると変数の部分的な初期化が発生するため、一時変数がないと、例外の観点からコードの安全性がさらに低下します。



 var tmp = new Person(); tmp.Name = "John"; tmp.Age = 42; var person = tmp;
      
      







この場合、いずれかのプロパティのセッターが例外でドロップすると、 _ personフィールドは既に初期化されていますが、最後までではなく、コンストラクタの使用に精通しているオブジェクト初期化子の「原子性」に違反します。



ただし、一時変数は多くの問題を解決しますが、 usingディレクティブ内でオブジェクト初期化子を使用する場合、これは発生しません。 ご存じのように、 usingディレクティブは次のコードに展開されます。



 var file = new FileStream("d:\\1.txt", FileMode.OpenOrCreate); try {} finally { if (file != null) ((IDisposable)file).Dispose(); }
      
      







ここで、2と2を追加すると、元の例は次のように展開されます。



 long position = -1; var tmpFile = new FileStream("d:\\1.txt", FileMode.OpenOrCreate); // !    ,  Dispose   ! tmpFile.Position = position; var file = tmpFile; try { } finally { if (file != null) ((IDisposable)file).Dispose(); }
      
      







これは、オブジェクト初期化子を使用して初期化されたプロパティが例外で失敗した場合、オブジェクトのDisposeメソッドが呼び出されないことを意味します。



おわりに



オブジェクト初期化子は便利な機能ですが、使用するために準備する必要もあります。 構文的に(そして意味的にも)、この機能はコンストラクターの呼び出しに非常に近いですが、残念ながら、それらは常に同等ではありません。 OOPの原則に関しては、オブジェクトのイニシャライザーを使用するタイミングと使用しないタイミングを決定するのはあなた次第ですが、例外とリソース管理のセキュリティに関しては、この機能がどのように機能し、これから何が起こるかを確実に理解する必要があります。



C#言語は、ほとんどの場合かなり予測可能な動作で区別されます(ただし、 可変で意味のあるタイプの微妙な例外などがあります )が、オブジェクトの初期化子ブロックを使用して混合することは直感的ではなく、この組み合わせがどのように設計されているかを理解することをお勧めします不注意。



All Articles