在对象构造之前发送对象的引用
本文关键字:对象 引用 | 更新日期: 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
的构造函数,该行为可能会变得定义不清。额外的构造函数指南还建议,不应该在构造函数中调用虚拟或抽象方法(在构造的类上),因为这可能会导致类似的后果,而行为可能很难推理。
这个问题的答案取决于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
同时包含对First
和Second
的引用,后两者之间的关系应该由Third
仲裁。)