Instantiating objects in the constructor

2019-03-19 19:20发布

问题:

Is there any benefit to doing the following:

public class Foo
{
    private Bar bar;

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

Instead of doing it like so:

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

    public Foo()
    {
    }
}

Given that at instantiation, the private member variable in either example will be instantiated, I don't believe there is a difference, but I've seen it enough times to where I'm curious.

回答1:

In the exact case you've given, there isn't a difference - but in general there is.

Variable initializers are executed before the base class constructor is called. If that base constructor calls a virtual method which uses some of the instance variables, you can see that difference.

For spec fans, it's in section 10.11.2 of the C# 4 spec:

When an instance constructor has no constructor initializer, or it has a constructor initializer of the form base(...), that constructor implicitly performs the initializations specified by the variable-initializers of the instance fields declared in its class. This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor and before the implicit invocation of the direct base class constructor.

Here's an example demonstrating this:

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();
    }
}

Result:

x=initialized in declaration; y=

Now having said the above, I would try hard to avoid calling virtual methods from a constructor. You're basically asking the derived class to work in a partially-initialized fashion. However, it's a difference you should be aware of.

As for where to initialize variables... I have to admit I'm not particularly consistent, and I don't find that's actually a problem. If I have any specific bias, it's probably to initialize anything which won't depend on any parameters at the point of declaration, leaving the variables which I can't initialize without extra information to the constructor.



回答2:

In your case, there's no real difference in the functionality. There is the problem of figuring out where and how everything gets initialized, though. If i put the initialization in the constructor, i have two big benefits:

  1. Everything's initialized in one place; i don't have to go hunting for whether and where it gets set.

  2. If i want, i can pass arguments to the Bar constructor based on how i'm setting stuff up. If the initializer's outside the constructor, i'm a lot more limited in how i can initialize stuff.

Frankly, the IDE helps a bit with #1...though it does yank me away from the code i was just looking at, my classes are rarely so huge as to make re-finding stuff an issue. So if i don't need #2, i'm just as likely to do it either way depending on the project and my mood. For bigger projects, though, i'd want to have all my init code in one place.

Edit:

OK, apparently there is a difference between the two. But the case where it matters is rare.

I've created the following classes:

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; }
}

Now, what i found was a bit surprising, and unexpected (if you haven't read the C# spec).

This is what the other class's constructor compiled to:

.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

Note the call to Program::.ctor (the base class's constructor) sandwiched between the two ldstr/stfld pairs (those are what set s1 and s2). Meaning, when the base constructor is running, s2 has not been set yet.

The program, for reference, outputs the following:

Hello
HelloWorld

because in Program's constructor, Console.WriteLine(obj) called obj.ToString(), which (since the object is already an other) was other::ToString(). Since s2 wasn't set yet, we didn't get a "World" the first time. If we were doing something more error prone than just printing stuff, this could cause real problems.

Now, this is an ugly example, designed to be a pathological case. But it's an excellent argument against calling virtual functions in your constructor. Without doing just that, this breakage would not have been possible. That's the only time you really have to worry about the difference: when your base class's constructor is calling virtual methods that you've overridden, that rely on the values of any fields that are being set in the constructor. A pretty narrow window of breakability, but yeah.



回答3:

Difference is in first case you have bar property initialized after constructor is called and in second before constructor is called. You don't use static methods so there is no difference.

A bit out of topic but best way is to initialise bar outside the object like this:

public class Foo
{
    private Bar bar;

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

This way you are not coupling your objects.



标签: c# oop