在构造函数中实例化对象

Ben*_*key 9 c# oop

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

public class Foo
{
    private Bar bar;

    public Foo()
    {
        bar = new Bar();
    }
}
Run Code Online (Sandbox Code Playgroud)

而不是这样做:

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

    public Foo()
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Jon*_*eet 19

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

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

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

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

这是一个证明这一点的例子:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

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

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

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

  • @cHao:危险在于你依赖*current*行为而不是*指定*行为.当然,规范并不总是*与真实行为同步,但我认为依靠它通常是更好的事情. (2认同)

cHa*_*Hao 5

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

  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; }
}
Run Code Online (Sandbox Code Playgroud)

现在,我发现的有点令人惊讶,而且意外(如果你还没有阅读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
Run Code Online (Sandbox Code Playgroud)

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

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

Hello
HelloWorld
Run Code Online (Sandbox Code Playgroud)

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

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