63

私たちのアプリケーションの1つで次のコードを見ました:

public class First()
{
      private Second _second;

      public First()
      {
          _second = new Second(this);
          // Doing some other initialization stuff,
      }

}

public class Second
{
    public Second(First f)
    {
    }
}

コンストラクターでは、完全に構築される前First()にクラスの参照を送信するのは悪いことではありませんか?制御ロジックがコンストラクターを離れて初めて、オブジェクトが完全に構築されると思います。First()

それともこれで大丈夫ですか?

4

8 に答える 8

71

私の質問は、First() コンストラクターで、完全に構築される前にクラス First() の参照を送信するのは悪いことではありませんか?

幾分。確かに、それは問題になる可能性があります。

Secondコンストラクターが後で使用するために参照を保持している場合、それはそれほど悪いことではありません。一方、Secondコンストラクターが にコールバックする場合First:

public Second(First f)
{
    f.DoSomethingUsingState();
}

...そして状態がまだ設定されていない場合、それはもちろん非常に悪いことです. で仮想メソッドを呼び出すと、Firstさらに悪化する可能性があります.コンストラクタ本体をまだ実行する機会さえないコードを呼び出すことになる可能性があります(ただし、変数初期化子は実行されます)

特に、readonlyフィールドは最初にある値で表示され、その後別の値で表示される場合があります...

少し前にこれについてブログを書いたので、さらに詳しい情報が得られるかもしれません。

もちろん、このようなことをしなければ、2 つの相互参照の不変オブジェクトを作成するのはかなり困難です...

于 2012-08-02T20:05:30.610 に答える
17

このパターンに遭遇した場合は、代わりにこれにリファクタリングできるかどうかを確認してください。

public class First()
{
      private Second _second;

      public First()
      {
          _second = new Second(this);
          // Doing some other initialization stuff,
      }

      private class Second
      {
          public Second(First f)
          {
          }
      }
}

依存関係をコンストラクターに渡すことは、2 つのクラス間の一種の密結合を意味します。First は、Second が何をしているのかを知っていることを信頼する必要があり、First の初期化されていない状態に依存しようとしないからです。この種の強い結合は、Second がプライベートのネストされたサブクラス (したがって、実装の詳細が明確) である場合、またはおそらくそれが内部クラスである場合により適切です。

于 2012-08-02T20:09:24.167 に答える
11

答えは、状況によって異なります。ただし、一般的に、これは潜在的な結果のために悪い考えと見なされます。

Secondより具体的には、しかし、それが構築される前から実際に何も使用しない限り、Firstあなたは大丈夫であるはずです。どういうわけかそれを保証できない場合は、間違いなく問題が発生する可能性があります。

于 2012-08-02T20:05:15.113 に答える
4

はい、やや悪いです。完全に初期化される前に のフィールドに対して何らかの操作を行うことができFirstます。これは、望ましくない、または未定義の動作を引き起こす可能性があります。

コンストラクターから仮想メソッドを呼び出すと、同じことが起こります。

于 2012-08-02T20:05:19.497 に答える
3

たとえばC++とは対照的に、CLRには完全に構築されたオブジェクトまたは不完全に構築されたオブジェクトの概念がありません。メモリアロケータがゼロ化されたオブジェクトを返すとすぐに、コンストラクタが実行される前に、(CLRの観点から)使用できるようになります。最終的なタイプがあり、仮想メソッドの呼び出しは最も派生したオーバーライドなどを呼び出します。thisコンストラクターの本体で使用したり、仮想メソッドを呼び出したりすることができます。これは実際に初期化の順序に問題を引き起こす可能性がありますが、CLRにはそれらを防ぐものはありません。

于 2012-08-02T20:10:40.220 に答える
1

あなたが説明したように、これが問題につながる可能性があることは事実です。_second = new Second(this);したがって、コメントで暗示されている他の初期化の後にのみコマンドを実行することをお勧めします。

2 つのオブジェクト間の相互参照を格納するための唯一のソリューションがこのパターンである場合がよくあります。ただし、多くの場合、これは、完全に初期化されていない可能性のあるインスタンスを受け取るクラスが、参照されるクラス (たとえば、同じ作成者によって記述された、同じアプリケーションの一部である、またはネストされたクラス、おそらくプライベート)。Secondこのような場合、 の作成者はの内部構造を知っている (または書いたことがある)ため、悪影響を回避できますFirst

于 2012-08-02T20:07:08.200 に答える
1

シナリオによって異なりますが、予測が困難な動作につながる可能性があります。ifSecondがコンストラクター内で何かを行う場合First、 のコンストラクターを変更すると、その動作が不明確になる可能性がありFirstます。追加のコンストラクター ガイダンスでは、コンストラクターで (構築されたクラスで) 仮想メソッドまたは抽象メソッドを呼び出すべきではないことも示唆しています。

于 2012-08-02T20:10:03.010 に答える
1

この質問に対する答えは、 と の関係の性質によって異なりFirstますSecond

それ自体が type のオブジェクトで構成されている (またはその初期化に必要な)別のオブジェクトで構成されているオブジェクトの種類を考えてみてくださいFirst。このような状況では、サイクルを含むオブジェクト グラフを作成することに注意する必要があります。

それにもかかわらず、オブジェクト グラフでサイクルが発生する正当な状況はたくさんあります。Firstの状態に依存して初期化を実行する場合Secondは、メソッドをそのままにしておく必要があり、通常は問題ありません。独自の初期化を実行するためSecondに の状態に依存している場合は、コンストラクターを次のように再配置する必要があります。First

  public First()
  {

      // Doing some other initialization stuff,
      _second = new Second(this);
  }

上記のステートメントの両方が真である場合 (Secondの状態に依存し、 の状態にFirst依存FirstするSecond)、ほぼ確実に設計を再検討し、 と の間の関係の性質をより正確に理解する必要がFirstありSecondます。(おそらく、 と のThird両方への参照を含むFirstオブジェクトが存在する必要がSecondあり、後者の 2 つの関係は によって調停される必要がありますThird。)

于 2012-08-02T20:13:38.770 に答える