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.
Odpowiedzi:
Moje zasady:
null
,false
,0
,0.0
...).źródło
default(T)
jest zawsze wartość, która ma wewnętrzną reprezentację binarną0
.foreach
pę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ćnew
podczas tworzenia nowych delegatów (jak było to wymagane w C # 1). Ale kto to robi? Język jest przeznaczony dla sumiennych programistów.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.
źródło
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.
źródło
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.
źródło
derivedObject.InheritedField
, to czy odnosi się ono do twojego podstawowego, czy pochodnego?Zakładając typ w twoim przykładzie, zdecydowanie wolisz inicjować pola w konstruktorze. Wyjątkowe przypadki to:
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.
źródło
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.
źródło
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.
Znam wartości
Dokładnie wiem, jakie wartości chcę mieć domyślnie lub muszę użyć innej logiki.
lub
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.
źródło
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.
źródło
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ą:
„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.
źródło
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.
źródło
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.
źródło
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).
źródło
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.
źródło
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).
Specjalna metoda inicjalizacji pola ogólnego do wartości domyślnej jest następująca:
źródło
Gdy nie potrzebujesz logiki ani obsługi błędów:
Gdy potrzebujesz logiki lub obsługi błędów:
Od https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html .
źródło