在构造函数中实例化对象

执行以下操作有什么好处:

public class Foo { private Bar bar; public Foo() { bar = new Bar(); } } 

而不是像这样做:

 public class Foo { private Bar bar = new Bar(); public Foo() { } } 

鉴于在实例化时,任何一个示例中的私有成员变量都将被实例化,我不相信存在差异,但我已经看到它足够多次到我好奇的地方。

在你给出的确切情况下,没有区别 – 但总的来说有。

调用基类构造函数之前执行变量初始值设定项。 如果该基础构造函数调用使用某些实例变量的虚方法,则可以看到这种差异。

对于规范粉丝,它在C#4规范的第10.11.2节中:

当实例构造函数没有构造函数初始值设定项,或者它具有forms为base(…)的构造函数初始值设定项时,该构造函数隐式执行由其类中声明的实例字段的变量初始值设定项指定的初始化。 这对应于在进入构造函数之后和直接调用直接基类构造函数之前立即执行的赋值序列。

这是一个certificate这一点的例子:

 using System; public class Base { public Base() { Dump(); } public virtual void Dump() {} } class Child : Base { private string x = "initialized in declaration"; private string y; public Child() { y = "initialized in constructor"; } public override void Dump() { Console.WriteLine("x={0}; y={1}", x, y); } } class Test { static void Main(string[] args) { new Child(); } } 

结果:

x =在声明中初始化; Y =

现在说了上面的内容,我会尽量避免从构造函数中调用虚方法。 您基本上要求派生类以部分初始化的方式工作。 但是,这应该是你应该知道的。

至于在哪里初始化变量……我不得不承认我不是特别一致,我发现这实际上不是问题。 如果我有任何特定的偏见,它可能初始化任何不依赖于声明点的任何参数的东西,留下我无法初始化的变量而没有额外的信息给构造函数。

在您的情况下,function没有真正的区别。 但是,有一个问题是弄清楚一切都被初始化的地方和方式。 如果我把初始化放在构造函数中,我有两大好处:

  1. 一切都在一个地方初始化; 我不必去寻找是否以及在何处设置。

  2. 如果我想,我可以根据我的设置方式将参数传递给Bar构造函数。 如果初始化程序在构造函数之外,我对如何初始化东西的限制更多。

坦率地说,IDE对#1有点帮助…虽然它让我远离我刚才看到的代码,我的课程很少会让重新找到问题成为一个问题。 因此,如果我不需要#2,我可能会根据项目和我的心情做任何一种方式。 但是,对于更大的项目,我希望将所有初始化代码放在一个地方。

编辑:

好的,显然这两者之间存在差异。 但重要的是罕见的情况。

我创建了以下类:

 class Program { public Program() { Console.WriteLine(this); } static void Main(string[] args) { other p = new other(); Console.WriteLine(p); Console.ReadLine(); } } class other : Program { string s1 = "Hello"; string s2; public other() { s2 = "World"; } public override string ToString() { return s1 + s2; } } 

现在,我发现的有点令人惊讶,而且意外(如果你还没有阅读C#规范)。

这是other类的构造函数编译为:

 .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 29 (0x1d) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldstr "Hello" IL_0006: stfld string ConsoleApplication1.other::s1 IL_000b: ldarg.0 IL_000c: call instance void ConsoleApplication1.Program::.ctor() IL_0011: ldarg.0 IL_0012: ldstr "World" IL_0017: stfld string ConsoleApplication1.other::s2 IL_001c: ret } // end of method other::.ctor 

注意调用Program ::。ctor(基类的构造函数)夹在两个ldstr / stfld对之间(那些是设置s1s2 )。 这意味着,当基本构造函数运行时,尚未设置s2

该计划作为参考,输出如下:

 Hello HelloWorld 

因为在Program的构造函数中, Console.WriteLine(obj)调用了obj.ToString() ,它(因为该对象已经是other )是other::ToString() 。 由于s2尚未确定,我们第一次没有获得“世界”。 如果我们做的事情比仅仅打印更容易出错,这可能会导致真正的问题。

现在,这是一个丑陋的例子,旨在成为病态案例。 但它是反对在构造函数中调用虚函数的一个很好的论据。 如果不这样做,就不可能实现这种破坏。 这是你唯一真正需要担心的区别:当你的基类的构造函数调用你已经重写的虚方法时,它依赖于构造函数中设置的任何字段的值。 一个非常狭窄的破碎窗口,但是。

差异在第一种情况下,在调用构造函数之后初始化bar属性,在调用构造函数之前在第二种情况下初始化。 您不使用静态方法,因此没有区别。

有点偏离主题,但最好的方法是在对象外面初始化条,如下所示:

 public class Foo { private Bar bar; public Foo( Bar bar ) { this.bar = bar; } } 

这样您就不会耦合对象。