Sınıf alanlarını yapıcıda mı yoksa bildirimde mi?


413

Son zamanlarda C # ve Java programlama ve sınıf alanları başlatmak için en iyi yer nerede merak ediyorum.

Beyanda mı yapmalıyım ?:

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

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

veya bir kurucuda ?:

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

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

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

Bazı gazilerinizin en iyi uygulama olduğunu düşündüklerini gerçekten merak ediyorum. Tutarlı olmak ve bir yaklaşıma bağlı kalmak istiyorum.


3
Yapılar için örnek alanı başlatıcılara sahip olamayacağınızı, bu nedenle yapıcıyı kullanmaktan başka seçeneğiniz olmadığını unutmayın.
yoyo

Yanıtlar:


310

Benim kurallarım:

  1. Bildiriminde varsayılan değerlerle başlatmak etmeyin ( null, false, 0, 0.0...).
  2. Alanın değerini değiştiren bir yapıcı parametreniz yoksa bildirimde başlatmayı tercih edin.
  3. Alanın değeri bir yapıcı parametresi nedeniyle değişirse, başlatmayı yapıcılara koyun.
  4. Uygulamanızda tutarlı olun (en önemli kural).

4
Kokoların üyeleri varsayılan değerlerine (0, yanlış, boş vb.) Başlatmamanız gerektiği anlamına gelir, çünkü derleyici bunu sizin için yapar (1.). Ancak bir alanı varsayılan değerinden başka bir değere başlatmak istiyorsanız, bunu bildirimde (2.) yapmalısınız. Sizi şaşırtan "varsayılan" kelimesinin kullanımı olabileceğini düşünüyorum.
Ricky Helgesson

95
Kural 1'e katılmıyorum - varsayılan bir değer belirtmeyerek (derleyici tarafından başlatılıp başlatılmadığına bakılmaksızın) geliştiriciyi belirli bir dil için varsayılan değerin ne olacağına dair tahmin veya belge aramaya bırakıyorsunuz . Okunabilirlik amacıyla, her zaman varsayılan değeri belirtirdim.
James

32
Bir türün varsayılan değeri default(T)her zaman iç ikili temsili olan değerdir 0.
Olivier Jacot-Descombes

15
Kural 1'i beğenip beğenmemeniz, kurucunun bittiği zaman açıkça başlatılması gereken salt okunur alanlarla kullanılamaz .
yoyo

36
Kural 1'e katılmayanlara katılmıyorum. Başkalarının C # dilini öğrenmesini beklemek sorun değil. Her bir foreachdöngüyü "listedeki tüm öğeler için aşağıdakileri tekrarlar" ifadesiyle yorumlamadığımız gibi , C # 'ın varsayılan değerlerini sürekli olarak yeniden ifade etmemiz gerekmez. Ayrıca C # 'ın başlatılmamış semantiği varmış gibi davranmamıza gerek yok. Bir değerin yokluğu açık bir anlam taşıdığından, onu dışarıda bırakmak iyidir. Açık olmak idealse, her newzaman yeni delegeler oluştururken kullanmalısınız (C # 1'de gerektiği gibi). Ama bunu kim yapıyor? Dil vicdan kodlayıcılar için tasarlanmıştır.
Edward Brey

149

C # 'da önemli değil. Verdiğiniz iki kod örneği tamamen eşdeğerdir. İlk örnekte C # derleyicisi (veya CLR mi?) Boş bir kurucu oluşturacak ve değişkenleri kurucudaymış gibi başlatacaktır (Jon Skeet'in aşağıdaki yorumlarda açıkladığı gibi küçük bir nüansı vardır). Zaten bir kurucu varsa, o zaman "yukarıdaki" herhangi bir başlatma onun üstüne taşınır.

En iyi uygulama açısından birincisi, bir başkası kolayca başka bir kurucu ekleyebilir ve onu zincirlemeyi unutabileceğinden, ikincisinden daha az hataya eğilimlidir.


4
Sınıfı GetUninitializedObject ile başlatmayı seçerseniz bu doğru değildir. Ctor'daki her şeye dokunulmayacak, ancak alan bildirimleri yürütülecek.
Wolf5

28
Aslında önemli. Temel sınıf yapıcısı, türetilmiş sınıfta geçersiz kılınan (genellikle kötü bir fikirdir, ancak olabilir) sanal bir yöntemi çağırırsa, örnek değişkeni başlatıcıları kullanılarak, yöntem çağrıldığında değişken başlatılır; oysa, yapıcı, olmayacaklar. (Örnek değişken başlatıcılar , temel sınıf yapıcısı çağrılmadan önce yürütülür .)
Jon Skeet

2
Gerçekten mi? Yemin ederim ki bu bilgiyi Richter'ın CLR'sinden C # (2. baskı sanırım) aracılığıyla yakaladım ve bunun özü, sözdizimsel şeker (yanlış okudum mu?) Ve CLR sadece değişkenleri yapıcıya sıkıştırdı. Ancak, durumun böyle olmadığını belirtiyorsunuz, yani üye başlatma, temel sandıkta sanal çağırma ve söz konusu sınıfta geçersiz kılma senaryosunda ctor başlatılmadan önce tetiklenebiliyor. Doğru anladım mı? Bunu buldun mu? 5 yıllık bir yazıdaki bu güncel yoruma şaşkındı (OMG 5 yıl oldu mu?).
Quibblesome

@Quibblesome: Bir alt sınıf kurucusu, üst kurucuya zincirleme bir çağrı içerecektir. Bir dil derleyicisi, üst yapıcıya tüm kod yollarında tam olarak bir kez çağrılması ve söz konusu çağrıdan önce yapım aşamasında olan nesnenin sınırlı kullanımı şartıyla, bundan önce istediği kadar çok veya az kod eklemekte serbesttir. C # ile ilgili sıkıntılarımdan biri, alanları başlatan kod ve yapıcı parametrelerini kullanan kod üretebilirken, yapıcı parametrelerine dayalı alanları başlatmak için hiçbir mekanizma sunmamasıdır.
supercat

1
@Quibblesome, değer atanır ve bir WCF yöntemine iletilirse, önceki sorun bir sorun olacaktır. Bu, verileri modelin varsayılan veri türüne sıfırlar. En iyi uygulama, yapıcıda (daha sonra bir) başlatma işlemini yapmaktır.
Pranesh Janarthanan

16

C # semantiği burada Java'dan biraz farklıdır. C # 'da deklarasyonda atama, üst sınıf yapıcısı çağrılmadan önce gerçekleştirilir. Java'da hemen yapılır, bu da 'bunun' kullanılmasına izin verir (özellikle anonim iç sınıflar için yararlıdır) ve iki formun anlambiliminin gerçekten eşleştiği anlamına gelir.

Yapabiliyorsanız alanları sonlandırın.


15

Sanırım bir uyarı var. Bir keresinde böyle bir hata yaptım: Türetilmiş bir sınıfın içinde, soyut bir temel sınıftan miras alan alanları "beyanda başlatmaya" çalıştım. Sonuç olarak, biri "temel" diğeri yeni bildirilen alanlar olmak üzere iki alan kümesi mevcuttu ve hata ayıklamak için oldukça zaman harcadım.

Ders: devralınan alanları başlatmak için bunu yapıcı içinde yaparsınız.


Eğer tarlaya göre atıfta bulunursanız derivedObject.InheritedField, üssünüze mi yoksa türetilene mi atıfta bulunur?
RayLuo

6

Örneğinizdeki türü varsayarsak, kesinlikle yapıcıdaki alanları başlatmayı tercih edin. İstisnai durumlar:

  • Statik sınıf / alanlardaki alanlar
  • Statik / final / et al olarak yazılan alanlar

Ben her zaman bir sınıfın üstündeki alan listesi içindekiler (burada bulunan, nasıl kullanılır değil) ve yapıcı giriş olarak düşünüyorum. Tabii yöntemler bölümlerdir.


Neden "kesinlikle" öyle? Aklını açıklamadan sadece bir stil tercihi sağladın. Oh bekleyin, 'ın @quibblesome göre, aldırma cevabını gerçekten sadece kişisel stil tercihi yüzden, 'tamamen eşdeğer' dir.
RayLuo

4

Ya sana söylersem duruma göre değişir?

Genel olarak her şeyi başlatır ve tutarlı bir şekilde yaparım. Evet aşırı açık ancak bakımı biraz daha kolay.

Performans konusunda endişeliysek, o zaman sadece ne yapılması gerektiğini başlatırım ve paranın en çok patladığı alanlara yerleştiririm.

Gerçek zamanlı bir sistemde, değişkene veya sabite bile ihtiyacım olup olmadığını soruyorum.

Ve C ++ 'da sık sık her iki yerde de başlatma yok ve bunu bir Init () işlevine taşımak. Neden? C ++ 'ta nesne yapımı sırasında istisna oluşturabilecek bir şey başlatıyorsanız, kendinizi bellek sızıntılarına açarsınız.


4

Çok ve çeşitli durumlar var.

Sadece boş bir listeye ihtiyacım var

Durum açık. Birisi listeye bir öğe eklediğinde sadece listemi hazırlamam ve bir istisnanın atılmasını önlemem gerekiyor.

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

    public CsvFile()
    {
    }
}

Değerleri biliyorum

Tam olarak varsayılan olarak hangi değerlere sahip olmak istediğimi biliyorum veya başka bir mantık kullanmam gerekiyor.

public class AdminTeam
{
    private List<string> usernames;

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

veya

public class AdminTeam
{
    private List<string> usernames;

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

Olası değerleri içeren boş liste

Bazen varsayılan olarak boş bir liste bekliyorum ve başka bir kurucu aracılığıyla değer ekleme olanağını bekliyorum.

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

    public AdminTeam()
    {
    }

    public AdminTeam(List<string> admins)
    {
         admins.ForEach(x => usernames.Add(x));
    }
}

3

Java'da, bildirime sahip bir başlatıcı, hangi kurucunun kullanıldığına (birden fazla varsa) veya yapıcılarınızın parametrelerine (bağımsız değişkenleri varsa) bakılmaksızın, alanın her zaman aynı şekilde başlatıldığı anlamına gelir. değeri değiştirin (son değilse). Bu nedenle, bir bildirim içeren bir başlatıcı kullanmak, bir okuyucunun, başlatılan değerin, hangi kurucunun kullanıldığına bakılmaksızın ve herhangi bir kurucuya iletilen parametrelerden bağımsız olarak, alanın her durumda sahip olduğu değer olduğunu gösterir. Bu nedenle, yalnızca tüm oluşturulmuş nesnelerin değeri aynı ise ve her zaman bildirimle birlikte bir başlatıcı kullanın.


3

C # tasarımı, satır içi başlatmanın tercih edildiğini veya dilde olmayacağını gösterir. Koddaki farklı yerler arasında çapraz referansı önlemek istediğinizde, genellikle daha iyi olursunuz.

En iyi performans için satır içi olması gereken statik alan başlatma ile tutarlılık da söz konusudur. Yapıcı Tasarım için Çerçeve Tasarım Yönergeleri şunları söylüyor:

✓ Çalışma zamanı, açıkça tanımlanmış bir statik kurucuya sahip olmayan türlerin performansını optimize edebildiğinden, statik alanları açıkça statik kurucular kullanmak yerine satır içi başlatır.

Bu bağlamda “düşünün”, yapmamak için iyi bir neden olmadığı sürece bunu yapmak anlamına gelir. Statik başlatıcı alanları söz konusu olduğunda, başlatmanın satır içi kodlanamayacak kadar karmaşık olması iyi bir nedendir.


2

Tutarlı olmak önemlidir, ancak bu kendinize sormanız gereken soru: "Başka bir şey için bir kurucum var mı?"

Tipik olarak, veri aktarımları için sınıfın kendisinin değişkenler için konut olarak çalışmak dışında hiçbir şey yapmadığı modeller oluşturuyorum.

Bu senaryolarda genellikle herhangi bir yöntemim veya kurucum yoktur. Listelerimi başlatmak için özel bir kurucu oluşturmak benim için aptalca olurdu, özellikle de bunları beyan ile uyumlu olarak başlatabildiğim için.

Diğerlerinin söylediği gibi, bu sizin kullanımınıza bağlıdır. Basit tutun ve yapmak zorunda olmadığınız ekstra bir şey yapmayın.


2

Birden fazla kurucunuzun olduğu durumu düşünün. Başlatma farklı kurucular için farklı olacak mı? Aynı olacaklarsa, neden her kurucu için tekrar etsin? Bu kokos ifadesiyle uyumludur, ancak parametrelerle ilgili olmayabilir. Örneğin, nesnenin nasıl oluşturulduğunu gösteren bir bayrak tutmak istediğinizi varsayalım. Daha sonra bu bayrak, yapıcı parametrelerine bakılmaksızın farklı kurucular için farklı şekilde başlatılır. Öte yandan, her bir kurucu için aynı başlatmayı tekrarlarsanız, (kurmadan) bazı kurucularda başlatma parametresini değiştirme, ancak diğerlerinde yapma olasılığınız kalmaz. Dolayısıyla, buradaki temel kavram, ortak kodun ortak bir konuma sahip olması ve farklı yerlerde potansiyel olarak tekrarlanmaması gerektiğidir.


1

Beyannamede değeri belirlemenin hafif bir faydası vardır. Yapıcıda ayarlarsanız, aslında iki kez ayarlanır (önce varsayılan değere, sonra da ctorda sıfırlayın).


2
C # 'da, alanlar her zaman önce varsayılan değer olarak ayarlanır. Bir başlatıcı varlığı, hiçbir fark yaratmaz.
Jeffrey L Whitledge

0

Normalde yapıcıyı bağımlılıkları almaktan ve onlarla ilgili örnek üyelerini başlatmaktan başka bir şey yapmamaya çalışırım. Bu, sınıflarınızı birim olarak test etmek istiyorsanız hayatınızı kolaylaştıracaktır.

Bir örnek değişkenine atayacağınız değer, kurucuya ileteceğiniz parametrelerin hiçbirinden etkilenmezse, bildirim zamanında atayın.


0

En iyi uygulama hakkında sorunuza doğrudan bir cevap değil, önemli ve ilgili bir tazeleme noktası, genel bir sınıf tanımı durumunda, varsayılan değerlerle başlatmak için derleyicide bırakmanız veya alanları başlatmak için özel bir yöntem kullanmamız gerektiğidir. varsayılan değerlerine (kod okunabilirliği için mutlaksa).

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

Ve genel bir alanı varsayılan değerine başlatmak için özel yöntem şöyledir:

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

    public MyGeneric()
    {           
        // The same method can be used here in constructor
    }
}

0

Bazı mantık veya hata işlemeye ihtiyacınız olmadığında:

  • Sınıf alanlarını bildirimde başlat

Bazı mantık veya hata işlemeye ihtiyacınız olduğunda:

  • Sınıf alanlarını yapıcıda başlat

Başlatma değeri kullanılabilir olduğunda ve başlatma bir satıra yerleştirilebildiğinde bu işe yarar. Bununla birlikte, bu başlatma şeklinin basitliği nedeniyle sınırlamaları vardır. Başlatma işlemi bir mantık gerektiriyorsa (örneğin, karmaşık bir diziyi doldurmak için hata işleme veya bir for döngüsü), basit atama yetersizdir. Örnek değişkenleri, hata işlemenin veya diğer mantığın kullanılabileceği kurucularda başlatılabilir.

Gönderen https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.