2011-01-23 2 views
8

Есть ли польза делать следующее:инстанцировании объекты в конструкторе

public class Foo 
{ 
    private Bar bar; 

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

Вместо того, чтобы делать это таким образом:

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

    public Foo() 
    { 
    } 
} 

Учитывая, что при конкретизации, частный переменной-члена либо в примере будет создан, я не верю, что есть разница, но я видел это достаточно времени, чтобы мне было любопытно.

+1

Возможный дубликат: http://stackoverflow.com/questions/4219759/create-an-object-in-the-constructor-or-at-top-of-the-class или: http: // stackoverflow. com/questions/298183/c-member-variable-initialization-best-practice –

+0

Спасибо, что поймали обманов, Yodan –

ответ

19

В конкретном случае, который вы указали, нет никакой разницы - но в целом есть.

Выполнены переменные инициализаторы до вызывается конструктор базового класса. Если этот базовый конструктор вызывает виртуальный метод, который использует некоторые из переменных экземпляра, вы можете видеть эту разницу.

Для спецификации вентиляторов, это в разделе 10.11.2 на С # 4 спецификации:

Когда конструктор экземпляра не имеет инициализатора конструктора, или оно имеет инициализатор конструктора вида основания (...) , этот конструктор неявно выполняет инициализации, заданные инициализаторами переменных полей экземпляра, объявленных в его классе. Это соответствует последовательности назначений, которые выполняются сразу после входа в конструктор и перед неявным вызовом конструктора прямого базового класса.

Вот пример, демонстрирующий это:

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

Результат:

х = инициализирован в декларации; y =

Теперь, сказав выше, я постараюсь избежать вызова виртуальных методов из конструктора. Вы в основном просите производный класс работать частично инициализированным образом. Однако это разница, о которой вы должны знать.

Что касается , где для инициализации переменных ... Я должен признать, что я не совсем последователен, и я не считаю, что это на самом деле проблема. Если у меня есть какой-то определенный уклон, возможно, это будет инициализировать все, что не будет зависеть от каких-либо параметров в точке объявления, оставив переменные, которые не могут не инициализировать без дополнительной информации конструктору.

+1

Bah. Должен был знать, что ты перезвонишь, пока я занят компиляцией и разборкой и объяснением. :) – cHao

+0

@cHao: Не нужно собирать и разбирать - проконсультируйтесь со спецификацией :) –

+0

@Jon: Eh. Меня раздражают люди, которые цитируют стандарты, как будто они могут быть единственным способом (tm). Конечно, в этом случае это более разумно; Я имею в виду, люди, которые его написали, также написал «Истинное воплощение». Я думаю, что я слишком долго висел вокруг людей C/C++. :) – cHao

5

В вашем случае нет никакой реальной разницы в функциональности. Однако есть проблема выяснить, где и как все инициализируется. Если я положил инициализацию в конструкторе, у меня есть два больших преимущества:

  1. Все инициализировано в одном месте; Мне не нужно идти на охоту за тем, будет ли и где он будет установлен.

  2. Если я хочу, я могу передать аргументы в конструктор Bar, основываясь на том, как я настраиваю содержимое. Если инициализатор вне конструктора, я намного более ограничен в том, как я могу инициализировать материал.

Честно говоря, IDE помогает немного с # 1 ... хотя это не дергать меня от кода я просто глядя на мои занятия редко бывают настолько велики, чтобы сделать повторное обнаружение вещи вопрос , Так что, если мне не нужно № 2, я так же могу сделать это в любом случае в зависимости от проекта и моего настроения. Тем не менее, для больших проектов я хочу, чтобы весь код init был в одном месте.

Edit:

ОК, по-видимому, есть разница между ними. Но случай, когда это имеет значение, встречается редко.

Я создал следующие классы:

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 

сведению призыв к Программе :: т е р (конструктор базового класса), зажатый между двумя ldstr/stfld пар (те, что установлено s1 и s2). Значение, когда работает базовый конструктор, s2 еще не установлен.

Программа, для справки, выводит следующее:

Hello 
HelloWorld 

, потому что в конструктор Программы, Console.WriteLine(obj) называется obj.ToString(), который (так как объект уже other) был other::ToString(). Поскольку s2 еще не был установлен, мы не получили «Мир» в первый раз. Если бы мы делали что-то более склонное к ошибкам, чем просто печатать материал, это могло бы вызвать реальные проблемы.

Теперь это уродливый пример, призванный быть патологическим случаем. Но это отличный аргумент против вызова виртуальных функций в вашем конструкторе. Не делая этого, этот полом был бы невозможен. Это единственный раз, когда вам действительно нужно беспокоиться о различии: когда конструктор базового класса вызывает виртуальные методы, которые вы переопределили, которые полагаются на значения любых полей, которые устанавливаются в конструкторе. Довольно узкое окно прерывистости, но да.

+0

Относительно # 2, конечно; это надуманный пример. Спасибо за примечание о чистоте кода.Я бы согласился, что он чище, но не был уверен, будет ли компилятор в надуманном примере обрабатывать каждый по-другому. (Звучит не так.) –

1

Разница в первом случае у вас есть свойство бара, инициализированное после вызова конструктора, а во втором - до вызова конструктора. Вы не используете статические методы, поэтому нет никакой разницы.

Немного не в тему, но лучше всего инициализирует бар за пределами объекта, как это:

public class Foo 
{ 
    private Bar bar; 

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

Таким образом, вы не связывания ваших объектов.