63

我在我们的一个应用程序中看到了以下代码:

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字段可能首先以一个值出现,然后以另一个值出现......

不久前我在博客上写过这个,它可能会提供更多信息。

当然,如果不做这种事情,很难创建两个相互引用的不可变对象......

于 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)
          {
          }
      }
}

将依赖项传递给构造函数意味着两个类之间存在一种紧密耦合,因为 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);仅在您的评论暗示的其他初始化内容之后运行命令。

很多时候,这种模式是存储两个对象之间相互引用的唯一解决方案。然而,在许多情况下,这发生在接收可能未完全初始化的实例的类与引用的类紧密耦合(例如,由同一作者编写;同一应用程序的一部分;或嵌套类,可能是私人的)。在这种情况下,可以避免负面影响,因为作者Second知道(或者甚至可能写过)First.

于 2012-08-02T20:07:08.200 回答
1

这取决于场景,但可能导致难以预测行为。如果在构造函数Second中做任何事情,First一旦你改变First. 其他构造函数指南还建议您不应在构造函数中调用虚拟或抽象方法(在构造的类上),因为它可能会导致类似的后果,其中行为可能难以推理。

于 2012-08-02T20:10:03.010 回答
1

这个问题的答案取决于 和 之间关系的First性质Second

想想什么样的对象可能由另一个对象组成,另一个对象本身由(或需要其初始化)类型的对象组成First。在这种情况下,您应该警惕创建带有循环的对象图。

然而,在很多合理的情况下,对象图中应该出现循环。如果First依赖状态Second来执行其初始化,那么您应该保持方法不变,这通常是可以的。如果Second依赖于状态First来执行自己的初始化,那么您可能应该重新排列构造函数:

  public First()
  {

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

如果上述两个陈述都是正确的(Second取决于 的状态First,并且First取决于 的状态Second),那么您几乎可以肯定地重新审视您的设计,并更准确地弄清楚 和 之间关系的First性质Second。(也许应该有一些对象Third包含对和的引用FirstSecond并且后两者之间的关系应该由 仲裁Third。)

于 2012-08-02T20:13:38.770 回答