Zainicjować pola klasy w konstruktorze czy w deklaracji?

413

Ostatnio programuję w języku C # i Javie i jestem ciekawy, gdzie najlepiej zainicjować pola klasy.

Czy powinienem to zrobić przy deklaracji ?:

public class Dice
{
    private int topFace = 1;
    private Random myRand = new Random();

    public void Roll()
    {
       // ......
    }
}

czy w konstruktorze ?:

public class Dice
{
    private int topFace;
    private Random myRand;

    public Dice()
    {
        topFace = 1;
        myRand = new Random();
    }

    public void Roll()
    {
        // .....
    }
}

Jestem naprawdę ciekawa, co niektórzy z was weterani uważają za najlepszą praktykę. Chcę być konsekwentny i trzymać się jednego podejścia.

mmcdole
źródło
3
Zauważ, że dla struktur nie możesz mieć inicjalizatorów pól instancji, więc nie masz wyboru, jak tylko użyć konstruktora.
yoyo

Odpowiedzi:

310

Moje zasady:

  1. Nie zainicjować z wartościami domyślnymi w deklaracji ( null, false, 0, 0.0...).
  2. Preferuj inicjalizację w deklaracji, jeśli nie masz parametru konstruktora, który zmienia wartość pola.
  3. Jeśli wartość pola zmienia się z powodu parametru konstruktora, należy zainicjować konstruktory.
  4. Bądź konsekwentny w swojej praktyce (najważniejsza zasada).
kokos
źródło
4
Oczekuję, że kokos oznacza, że ​​nie powinieneś inicjować członków do wartości domyślnych (0, false, null itp.), Ponieważ kompilator zrobi to za ciebie (1.). Ale jeśli chcesz zainicjować pole na wartość inną niż jego wartość domyślna, powinieneś to zrobić w deklaracji (2.). Myślę, że może to być zamieszanie przez użycie słowa „default”.
Ricky Helgesson,
95
Nie zgadzam się z regułą 1 - nie określając wartości domyślnej (niezależnie od tego, czy jest ona inicjowana przez kompilator, czy nie), pozostawiasz programistom zgadnięcie lub poszukiwanie dokumentacji na temat domyślnej wartości dla tego konkretnego języka. Dla celów czytelności zawsze podawałbym wartość domyślną.
James
32
Domyślną wartością typu default(T)jest zawsze wartość, która ma wewnętrzną reprezentację binarną 0.
Olivier Jacot-Descombes
15
Niezależnie od tego, czy podoba Ci się reguła 1, nie można jej używać z polami tylko do odczytu, które muszą zostać jawnie zainicjowane do czasu ukończenia konstruktora.
yoyo
36
Nie zgadzam się z tymi, którzy nie zgadzają się z zasadą 1. Można oczekiwać, że inni nauczą się języka C #. Ponieważ nie komentujemy każdej foreachpętli słowem „powtarza to dla wszystkich elementów na liście”, nie musimy ciągle przekształcać domyślnych wartości C #. Nie musimy też udawać, że C # ma niezainicjowaną semantykę. Ponieważ brak wartości ma wyraźne znaczenie, można ją pominąć. Jeśli jest to idealne, aby być jawnym, należy zawsze używać newpodczas tworzenia nowych delegatów (jak było to wymagane w C # 1). Ale kto to robi? Język jest przeznaczony dla sumiennych programistów.
Edward Brey
149

W języku C # to nie ma znaczenia. Dwie próbki kodu, które podajesz, są całkowicie równoważne. W pierwszym przykładzie kompilator C # (czy jest to CLR?) Zbuduje pusty konstruktor i zainicjuje zmienne tak, jakby były w konstruktorze (istnieje niewielka nuta, którą Jon Skeet wyjaśnia w komentarzach poniżej). Jeśli istnieje już konstruktor, każda inicjalizacja „powyżej” zostanie przeniesiona na jego szczyt.

Pod względem najlepszej praktyki ta pierwsza jest mniej podatna na błędy niż druga, ponieważ ktoś może łatwo dodać innego konstruktora i zapomnieć o łańcuchu.

Quibblesome
źródło
4
To nie jest poprawne, jeśli zdecydujesz się zainicjować klasę za pomocą GetUninitializedObject. Cokolwiek jest w ctor, nie zostanie zmienione, ale zostaną uruchomione deklaracje polowe.
Wolf5
28
Właściwie to ma znaczenie. Jeśli konstruktor klasy bazowej wywoła metodę wirtualną (co jest generalnie złym pomysłem, ale może się zdarzyć), która jest nadpisana w klasie pochodnej, to przy użyciu inicjatorów zmiennych instancji zmienna zostanie zainicjowana po wywołaniu metody - podczas gdy użycie inicjalizacji w Konstruktor, nie będą. (Inicjatory zmiennych instancji są wykonywane przed wywołaniem konstruktora klasy bazowej.)
Jon Skeet,
2
Naprawdę? Przysięgam, że pobierałem te informacje z CLR Richtera za pośrednictwem C # (myślę, że wydanie 2), a jego sedno było takie, że był to cukier składniowy (być może źle go odczytałem?), A CLR po prostu zablokował zmienne w konstruktorze. Ale twierdzisz, że tak nie jest, tzn. Inicjalizacja elementu członkowskiego może zostać uruchomiona przed inicjalizacją ctor w szalonym scenariuszu wywołania wirtualnego w ctor bazowym i zastąpienia w danej klasie. Czy zrozumiałem poprawnie? Właśnie to odkryłeś? Zdziwiony tym aktualnym komentarzem do pięcioletniego posta (OMG, czy minęło już 5 lat?).
Quibblesome
@Quibblesome: Konstruktor klasy potomnej będzie zawierał połączenie łańcuchowe z konstruktorem nadrzędnym. Kompilator językowy może dowolnie zawierać tyle lub mniej kodu, ile chce, pod warunkiem, że konstruktor nadrzędny jest wywoływany dokładnie raz na wszystkich ścieżkach kodu, a obiekt będący w budowie jest w ograniczonym stopniu używany przed wywołaniem. Jedną z moich niedogodności związanych z C # jest to, że chociaż może generować kod inicjujący pola i kod, który wykorzystuje parametry konstruktora, nie oferuje żadnego mechanizmu inicjowania pól na podstawie parametrów konstruktora.
supercat
1
@Quibblesome, poprzedni będzie problemem, jeśli wartość zostanie przypisana i przekazana do metody WCF. Co spowoduje zresetowanie danych do domyślnego typu danych modelu. Najlepszą praktyką jest inicjalizacja w konstruktorze (późniejsza).
Pranesh Janarthanan
16

Semantyka C # nieznacznie różni się tutaj od Javy. W języku C # przypisanie w deklaracji jest wykonywane przed wywołaniem konstruktora nadklasy. W Javie odbywa się to natychmiast, po czym pozwala na użycie „tego” (szczególnie przydatne dla anonimowych klas wewnętrznych) i oznacza, że ​​semantyka tych dwóch form naprawdę się zgadza.

Jeśli możesz, ustaw pola jako ostateczne.

Tom Hawtin - tackline
źródło
15

Myślę, że jest jedno zastrzeżenie. Kiedyś popełniłem taki błąd: w klasie pochodnej próbowałem „zainicjować w deklaracji” pola odziedziczone po abstrakcyjnej klasie bazowej. Rezultat był taki, że istniały dwa zestawy pól, jeden to „baza”, a drugi to nowo zadeklarowane, a debugowanie mnie kosztowało sporo czasu.

Lekcja: aby zainicjować dziedziczone pola, zrobiłbyś to w konstruktorze.

Xji
źródło
Jeśli więc odwołujesz się do pola derivedObject.InheritedField, to czy odnosi się ono do twojego podstawowego, czy pochodnego?
RayLuo
6

Zakładając typ w twoim przykładzie, zdecydowanie wolisz inicjować pola w konstruktorze. Wyjątkowe przypadki to:

  • Pola w klasach / metodach statycznych
  • Pola wpisane jako statyczne / końcowe / i in

Zawsze myślę o liście pól na szczycie klasy jako o spisie treści (co jest tu zawarte, a nie o tym, jak jest używany), a o konstruktorze jako wprowadzeniu. Metodami są oczywiście rozdziały.

kolęda
źródło
Dlaczego tak jest „zdecydowanie”? Podałeś tylko preferencje dotyczące stylu, bez wyjaśnienia jego uzasadnienia. Oh wait, nieważne, zgodnie z @quibblesome „s odpowiedź , że są«całkowicie równoważne», więc jest to naprawdę tylko preferencja osobisty styl.
RayLuo
4

Co jeśli ci powiem, to zależy?

Generalnie wszystko inicjuję i robię to w spójny sposób. Tak, jest to zbyt wyraźne, ale jest też trochę łatwiejsze w utrzymaniu.

Jeśli martwimy się wydajnością, to inicjuję tylko to, co trzeba zrobić, i umieszczam to w obszarach, które dają największe zyski.

W systemie czasu rzeczywistego pytam, czy w ogóle potrzebuję zmiennej lub stałej.

A w C ++ często nie inicjuję żadnego miejsca i przenoszę go do funkcji Init (). Dlaczego? Cóż, w C ++, jeśli inicjujesz coś, co może zgłosić wyjątek podczas budowy obiektu, otwierasz się na wycieki pamięci.

Dan Blair
źródło
4

Istnieje wiele różnych sytuacji.

Potrzebuję tylko pustej listy

Sytuacja jest jasna. Muszę tylko przygotować moją listę i zapobiec generowaniu wyjątku, gdy ktoś doda element do listy.

public class CsvFile
{
    private List<CsvRow> lines = new List<CsvRow>();

    public CsvFile()
    {
    }
}

Znam wartości

Dokładnie wiem, jakie wartości chcę mieć domyślnie lub muszę użyć innej logiki.

public class AdminTeam
{
    private List<string> usernames;

    public AdminTeam()
    {
         usernames = new List<string>() {"usernameA", "usernameB"};
    }
}

lub

public class AdminTeam
{
    private List<string> usernames;

    public AdminTeam()
    {
         usernames = GetDefaultUsers(2);
    }
}

Pusta lista z możliwymi wartościami

Czasami domyślnie oczekuję pustej listy z możliwością dodawania wartości za pośrednictwem innego konstruktora.

public class AdminTeam
{
    private List<string> usernames = new List<string>();

    public AdminTeam()
    {
    }

    public AdminTeam(List<string> admins)
    {
         admins.ForEach(x => usernames.Add(x));
    }
}
Miroslav Holec
źródło
3

W Javie inicjator z deklaracją oznacza, że ​​pole jest zawsze inicjowane w ten sam sposób, bez względu na to, który konstruktor jest używany (jeśli masz więcej niż jeden) lub parametry swoich konstruktorów (jeśli mają argumenty), chociaż konstruktor może następnie zmień wartość (jeśli nie jest ostateczna). Zatem użycie inicjalizatora z deklaracją sugeruje czytelnikowi, że zainicjowana wartość jest wartością, którą pole ma we wszystkich przypadkach , niezależnie od tego, który konstruktor jest używany i niezależnie od parametrów przekazanych do dowolnego konstruktora. Dlatego używaj inicjalizatora z deklaracją tylko wtedy i zawsze wtedy, gdy wartość dla wszystkich zbudowanych obiektów jest taka sama.

Raedwald
źródło
3

Projekt C # sugeruje, że preferowana jest inicjalizacja inline, inaczej nie będzie w języku. Za każdym razem, gdy możesz uniknąć odniesienia między różnymi miejscami w kodzie, ogólnie lepiej.

Istnieje również kwestia spójności z inicjalizacją pola statycznego, która musi być wbudowana w celu uzyskania najlepszej wydajności. Wytyczne dotyczące projektowania ram dla projektowania konstruktorów mówią:

✓ UWAŻAJ, że inicjowanie pól statycznych jest wbudowane zamiast jawnie przy użyciu konstruktorów statycznych, ponieważ środowisko wykonawcze jest w stanie zoptymalizować wydajność typów, które nie mają wyraźnie zdefiniowanego konstruktora statycznego.

„Rozważ” w tym kontekście oznacza, że ​​należy to zrobić, chyba że istnieje dobry powód, aby tego nie robić. W przypadku statycznych pól inicjalizujących dobrym powodem jest to, że inicjalizacja jest zbyt złożona, aby można było ją zakodować bezpośrednio.

Edward Brey
źródło
2

Bycie konsekwentnym jest ważne, ale oto pytanie, które należy sobie zadać: „Czy mam konstruktora do czegoś innego?”

Zazwyczaj tworzę modele do przesyłania danych, w których sama klasa nie robi nic poza pracą jako obudową dla zmiennych.

W tych scenariuszach zwykle nie mam żadnych metod ani konstruktorów. Głupie byłoby dla mnie stworzenie konstruktora do wyłącznego celu inicjowania moich list, zwłaszcza że mogę je inicjować zgodnie z deklaracją.

Tak jak wielu innych powiedziało, zależy to od twojego użycia. Uprość to i nie rób niczego więcej, czego nie musisz.

MeanJerry
źródło
2

Rozważ sytuację, w której masz więcej niż jednego konstruktora. Czy inicjalizacja będzie różna dla różnych konstruktorów? Jeśli będą takie same, to po co powtarzać dla każdego konstruktora? Jest to zgodne z instrukcją kokos, ale może nie być związane z parametrami. Powiedzmy na przykład, że chcesz zachować flagę, która pokazuje, w jaki sposób obiekt został utworzony. Następnie ta flaga byłaby inicjowana inaczej dla różnych konstruktorów, niezależnie od parametrów konstruktora. Z drugiej strony, jeśli powtórzysz tę samą inicjalizację dla każdego konstruktora, pozostawiasz możliwość (niezamierzonej) zmiany parametru inicjalizacji w niektórych konstruktorach, ale w innych nie. Zatem podstawową koncepcją jest to, że wspólny kod powinien mieć wspólną lokalizację i nie może być potencjalnie powtarzany w różnych lokalizacjach.

Joe Programmer
źródło
1

Ustawienie wartości w deklaracji ma niewielką zaletę. Jeśli ustawisz go w konstruktorze, tak naprawdę jest on ustawiany dwukrotnie (najpierw do wartości domyślnej, a następnie zresetuj w ctor).

John Meagher
źródło
2
W języku C # pola są zawsze najpierw ustawiane jako wartość domyślna. Obecność inicjatora nie ma znaczenia.
Jeffrey L Whitledge
0

Zwykle staram się, aby konstruktor nie robił nic poza pobieraniem zależności i inicjowaniem z nimi powiązanych elementów instancji. Ułatwi ci to życie, jeśli chcesz testować jednostkowe swoje zajęcia.

Jeśli na wartość, którą zamierzasz przypisać do zmiennej instancji, nie wpływa żaden z parametrów, które przekażesz konstruktorowi, przypisz ją w czasie deklaracji.

Iker Jimenez
źródło
0

Nie jest to bezpośrednia odpowiedź na twoje pytanie dotyczące najlepszych praktyk, ale ważnym i powiązanym punktem odświeżającym jest to, że w przypadku ogólnej definicji klasy albo pozostaw kompilatorowi zainicjowanie wartości domyślnych, albo musimy użyć specjalnej metody inicjalizacji pól do ich wartości domyślnych (jeśli jest to absolutnie konieczne do odczytu kodu).

class MyGeneric<T>
{
    T data;
    //T data = ""; // <-- ERROR
    //T data = 0; // <-- ERROR
    //T data = null; // <-- ERROR        

    public MyGeneric()
    {
        // All of the above errors would be errors here in constructor as well
    }
}

Specjalna metoda inicjalizacji pola ogólnego do wartości domyślnej jest następująca:

class MyGeneric<T>
{
    T data = default(T);

    public MyGeneric()
    {           
        // The same method can be used here in constructor
    }
}
użytkownik1451111
źródło
0

Gdy nie potrzebujesz logiki ani obsługi błędów:

  • Zainicjuj pola klasy w deklaracji

Gdy potrzebujesz logiki lub obsługi błędów:

  • Zainicjuj pola klasy w konstruktorze

Działa to dobrze, gdy dostępna jest wartość inicjalizacji, a inicjalizacja może być umieszczona w jednym wierszu. Ta forma inicjalizacji ma jednak ograniczenia ze względu na swoją prostotę. Jeśli inicjalizacja wymaga pewnej logiki (na przykład obsługi błędów lub pętli for w celu wypełnienia złożonej tablicy), proste przypisanie jest nieodpowiednie. Zmienne instancji można zainicjować w konstruktorach, w których można zastosować obsługę błędów lub inną logikę.

Od https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html .

Yousha Aleayoub
źródło