在构造函数中实例化对象

时间:2011-01-23 07:45:11

标签: c# oop

执行以下操作是否有任何好处:

public class Foo
{
    private Bar bar;

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

而不是这样做:

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

    public Foo()
    {
    }
}

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

3 个答案:

答案 0 :(得分: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();
    }
}

结果:

  

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

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

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

答案 1 :(得分:5)

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

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

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

  3. 坦率地说,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。由于{{1}}尚未确定,我们第一次没有获得“世界”。如果我们做的事情比仅仅打印更容易出错,这可能会导致真正的问题。

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

答案 2 :(得分:1)

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

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

public class Foo
{
    private Bar bar;

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

这样你就不会耦合你的物体了。