C# 在对象构造之前发送其引用

C# 在对象构造之前发送其引用,c#,reference,constructor,C#,Reference,Constructor,我在我们的一个应用程序中看到了以下代码: public class First() { private Second _second; public First() { _second = new Second(this); // Doing some other initialization stuff, } } public class Second { public Second(Firs

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

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


或者这样可以吗?

答案是,视情况而定。但是,由于潜在的后果,这通常被认为是一个坏主意


但是,更具体地说,只要在构建前
Second
没有实际使用
First
中的任何内容,那么您应该可以。但是,如果你不能以某种方式保证,你肯定会遇到问题。

是的,这有点糟糕。可以在
的字段完全初始化之前先对其执行操作,这将导致不希望的或未定义的行为

从构造函数调用虚方法时也会发生同样的情况

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

有点。当然,这可能是个问题

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

public Second(First f)
{
    f.DoSomethingUsingState();
}
。。。而国家还没有建立起来,那当然是一件非常糟糕的事情。如果您首先在
上调用一个虚拟方法,那么情况可能会更糟-您可能会调用一些甚至还没有机会运行其构造函数体的代码(尽管它的变量初始值设定项将被执行)

特别是,
readonly
字段可能首先显示一个值,然后再显示另一个值

我刚才提到过,这可能会提供更多的信息


当然,如果不这样做,很难创建两个相互引用的不可变对象…

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


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

的内部结构。如果您遇到这种模式,您可以检查是否可以将其重构为:

public class First()
{
      private Second _second;

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

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

将依赖项传递给构造函数意味着两个类之间存在一种紧密耦合,因为第一个类必须相信第二个类知道它在做什么,并且不会试图依赖第一个类的未初始化状态。当Second是一个私有嵌套子类(因此是一个清晰的实现细节)或可能是一个内部类时,这种强耦合更合适。

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

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

这个问题的答案取决于
第一个
第二个
之间关系的性质

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

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

  public First()
  {

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

如果上述两种说法都是正确的(
Second
取决于
First
的状态,
First
取决于
Second
的状态),那么几乎可以肯定的是,您应该重新审视您的设计,并更准确地找出
First
Second
之间关系的本质。(可能应该有某个对象
Third
,其中包含对
第一个
第二个
的引用,后两者之间的关系应由
Third
仲裁)

如果您可以保证在对象的构造函数完成之前不会访问对first的引用,那么就可以了。如果不能保证,那么您可能会在访问不可靠的内容时遇到问题