2011-01-23 15 views
8

Czy jest jakaś korzyść, wykonując następujące czynności:instancji obiektów w konstruktorze

public class Foo 
{ 
    private Bar bar; 

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

Zamiast robić to tak:

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

    public Foo() 
    { 
    } 
} 

Biorąc pod uwagę, że w instancji, zmienna członek prywatnych bądź przykładem będzie utworzony, nie sądzę, że jest różnica, ale widziałem to wystarczająco dużo razy, gdy jestem ciekawy.

+1

Możliwy duplikat: http://stackoverflow.com/questions/4219759/create-an-object-in-the-constructor-or-at-top-the-class lub: http: // stackoverflow. com/questions/298183/c-member-variable-initialization-best-practice –

+0

Dzięki za złapanie dupków, Yodan –

Odpowiedz

19

W dokładnie tym, co podałeś, nie ma różnicy - ale ogólnie jest.

Inicjatory zmiennych są wykonywane przed wywoływany jest konstruktor klasy podstawowej. Jeśli ten bazowy konstruktor wywoła metodę wirtualną, która używa niektórych zmiennych instancji, można zauważyć tę różnicę.

Specyfikację wentylatorów, jest w części 10.11.2 z C# 4 specyfikacji:

Kiedy konstruktor przykład ma konstruktora inicjatora lub inicjatora ma konstruktora podstawy formy (...) , ten konstruktor niejawnie wykonuje inicjalizacje określone przez zmienne-inicjatory pól instancji zadeklarowanych w klasie. Odpowiada to sekwencji przydziałów, które są wykonywane natychmiast po wejściu do konstruktora i przed niejawnym wywołaniem bezpośredniego konstruktora klasy podstawowej.

Oto przykład wykazać następująco:

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

Wynik:

X = inicjalizować zgłoszenia; y =

Powiedziawszy to powyżej, starałbym się unikać wywoływania metod wirtualnych od konstruktora. Zasadniczo prosisz, aby klasa pochodna działała w częściowo zainicjalizowany sposób. Jest to jednak różnica, o której powinieneś wiedzieć.

Jeśli chodzi o , gdzie, aby zainicjować zmienne ... Muszę przyznać, że nie jestem szczególnie konsekwentny, i nie uważam, że to w rzeczywistości problem. Jeśli mam jakieś konkretne odchylenie, prawdopodobnie zainicjuję wszystko, co nie będzie zależeć od parametrów w miejscu deklaracji, pozostawiając zmienne, które nie mogą zainicjować bez dodatkowych informacji do konstruktora.

+1

Bah. Powinieneś wiedzieć, że zadzwonisz, gdy będę zajęty kompilowaniem, demontażem i wyjaśnianiem. :) – cHao

+0

@cHao: Nie trzeba kompilować i rozmontowywać - sprawdź specyfikację :) –

+0

@Jon: Eh. Denerwują mnie ludzie, którzy podają standardy tak, jakby były jedyną możliwą rzeczą (tm). Oczywiście w tym przypadku to ma więcej sensu; to znaczy, ludzie, którzy to napisali, napisali także One True Implementation. Wydaje mi się, że zbyt długo kręciłem się po C/C++. :) – cHao

5

W twoim przypadku nie ma żadnej różnicy w funkcjonalności. Istnieje problem z ustaleniem, gdzie i jak wszystko zostanie zainicjalizowane. Jeśli wstawię inicjalizację do konstruktora, mam dwie duże zalety:

  1. Wszystko zostało zainicjowane w jednym miejscu; Nie muszę polować na to, czy i gdzie to się skończy.

  2. Jeśli chcę, mogę przekazywać argumenty do konstruktora prętów w oparciu o sposób ustawiania rzeczy. Jeśli inicjator jest poza konstruktorem, jestem znacznie bardziej ograniczony w tym, jak mogę zainicjować rzeczy.

Szczerze mówiąc, IDE pomaga trochę z nr 1 ... chociaż to nie szarpać mnie od kodu i po prostu patrząc na moje zajęcia rzadko są tak ogromne jak zrobić ponowne odkrycie rzeczy problem . Więc jeśli nie potrzebuję 2, równie dobrze mogę to zrobić w dowolny sposób, w zależności od projektu i nastroju. W przypadku większych projektów chciałbym mieć cały mój kod init w jednym miejscu.

Edit:

OK, najwyraźniej istnieje różnica między tymi dwoma. Ale przypadek, w którym ma to znaczenie, jest rzadkością.

Utworzyłem następujących klas:

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

Teraz, co uważam, że było nieco zaskakujące i nieoczekiwane (jeśli nie czytałeś # specyfikację C).

To co konstruktora klasy other skompilowany.

.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 

Uwaga wezwanie do programowania :: konstruktor (konstruktora klasy bazowej) umieszczonej pomiędzy dwoma ldstr/stfld par (są co ustawić s1 i s2). Znaczenie: kiedy konstruktor podstawowy działa, s2 nie został jeszcze ustawiony.

Program, w celach informacyjnych, wyprowadza następujące:

Hello 
HelloWorld 

ponieważ konstruktor programu, Console.WriteLine(obj) nazywa obj.ToString(), która (ponieważ obiekt jest już other) był other::ToString(). Ponieważ s2 nie zostało jeszcze ustalone, po raz pierwszy nie otrzymaliśmy "Świata". Jeśli robimy coś bardziej podatnego na błędy niż zwykłe drukowanie, może to spowodować poważne problemy.

To jest brzydki przykład, mający być patologicznym przypadkiem. Ale jest to doskonały argument przeciwko wywoływaniu funkcji wirtualnych w twoim konstruktorze. Bez tego tylko złamanie tego nie byłoby możliwe. To jedyny czas, w którym naprawdę musisz się martwić o różnicę: kiedy twój konstruktor klasy podstawowej wywołuje metody wirtualne, które przesłoniłeś, które polegają na wartościach wszystkich pól ustawianych w konstruktorze. To dość wąskie okno z breakability, ale tak.

+0

Odnośnie numeru 2, oczywiście; jest to wymyślny przykład. Dzięki za uwagę na czystość kodu.Zgadzam się, że jest to czystsze, ale nie byłem pewien, czy kompilator w wymyślonym przykładzie potraktuje każdy w inny sposób. (Nie brzmi tak.) –

1

Różnica polega na tym, że masz pierwszą właściwość paska zainicjowaną po wywołaniu konstruktora, a na drugim przed wywołaniem konstruktora. Nie używasz statycznych metod, więc nie ma różnicy.

Nieco na temat, ale najlepszym sposobem jest do zainicjowania poprzeczkę poza obiektem tak:

public class Foo 
{ 
    private Bar bar; 

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

ten sposób nie są sprzężenie swoich obiektów.

Powiązane problemy