在对象构造之前发送对象的引用

本文关键字:对象 引用 | 更新日期: 2024-09-23 18:01:00

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

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()之前发送它的引用,这不是很糟糕吗?我认为只有当控制逻辑离开构造函数时,对象才被完全构造。

或者这样可以吗?

在对象构造之前发送对象的引用

我的问题是,在First()构造函数中,在类First()被完全构造之前,我们发送它的引用,这不是很糟糕吗?

有点。这肯定会成为一个问题。

如果Second构造函数只是保留一个引用以供以后使用,那还不错。另一方面,如果Second构造函数回调到First:

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

而这个州还没有建立起来,那么这当然是一件非常糟糕的事情。如果你在First上调用一个虚拟方法,那么情况可能会更糟——你最终可能会调用一些代码,这些代码甚至还没有机会运行的任何构造函数主体(尽管它的变量初始化器已经执行)。

特别地,readonly字段可以首先看到一个值,然后看到另一个值。。。

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

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

如果您遇到这种模式,您可能会检查它是否可以重构为:

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是一个私有嵌套子类(因此是一个明确的实现细节),或者可能是一个内部类时,这种强耦合更合适。

答案是,这取决于情况。然而,一般来说,由于潜在的后果,这将被认为是一个坏主意。

不过,更具体地说,只要Second在构造之前实际上没有使用First中的任何内容,那么您就应该没事了。如果你不能以某种方式保证这一点,你肯定会遇到问题。

是的,它有点糟糕。可以在First的字段完全初始化之前对其进行处理,这将导致不希望的或未定义的行为。

当您从构造函数中调用虚拟方法时,也会发生同样的情况。

与C++相比,CLR没有完全构造或不完全构造对象的概念。一旦内存分配器返回一个零对象,在构造函数运行之前,它就可以使用了(从CLR的角度来看)。它有它的最终类型,对虚拟方法的调用调用最派生的重写等。你可以在构造函数的主体中使用this,调用虚拟方法等。这确实可能会导致初始化顺序的问题,但CLR中没有任何东西可以阻止它们。

确实,这可能会导致您所描述的问题。因此,通常建议仅在注释所暗示的其他初始化内容之后运行命令(如_second = new Second(this);)。

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

这取决于场景,但可能导致难以预测行为。如果Second在构造函数中对First执行任何操作,那么一旦更改First的构造函数,该行为可能会变得定义不清。额外的构造函数指南还建议,不应该在构造函数中调用虚拟或抽象方法(在构造的类上),因为这可能会导致类似的后果,而行为可能很难推理。

这个问题的答案取决于FirstSecond之间关系的性质。

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

尽管如此,在许多合理的情况下,循环应该发生在对象图中。如果First依赖于Second的状态来执行初始化,那么您应该保持该方法的原样,这通常是可以的。如果Second依赖于First的状态来执行自己的初始化,那么您可能应该重新排列构造函数,如下所示:

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

如果前面的两种说法都是真的(Second取决于First的状态,First取决于Second的状态),那么几乎可以肯定的是,您应该重新审视您的设计,并更准确地了解FirstSecond之间关系的性质。(也许应该有一些对象Third同时包含对FirstSecond的引用,后两者之间的关系应该由Third仲裁。)