Inisialisasi bidang kelas dalam konstruktor atau deklarasi?


413

Saya sudah pemrograman di C # dan Java baru-baru ini dan saya ingin tahu di mana tempat terbaik untuk menginisialisasi bidang kelas saya.

Haruskah saya melakukannya di deklarasi ?:

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

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

atau di konstruktor ?:

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

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

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

Saya benar-benar ingin tahu apa yang menurut sebagian dari Anda veteran adalah praktik terbaik. Saya ingin konsisten dan tetap pada satu pendekatan.


3
Perhatikan bahwa untuk struct Anda tidak dapat memiliki inisialisasi bidang contoh, jadi Anda tidak punya pilihan selain menggunakan konstruktor.
yoyo

Jawaban:


310

Aturan saya:

  1. Jangan menginisialisasi dengan nilai default dalam deklarasi ( null, false, 0, 0.0...).
  2. Lebih suka inisialisasi dalam deklarasi jika Anda tidak memiliki parameter konstruktor yang mengubah nilai bidang.
  3. Jika nilai bidang berubah karena parameter konstruktor, letakkan inisialisasi pada konstruktor.
  4. Konsisten dalam latihan Anda (aturan paling penting).

4
Saya berharap bahwa kokos berarti Anda tidak boleh menginisialisasi anggota ke nilai default (0, false, null, dll.), Karena kompiler akan melakukan itu untuk Anda (1.). Tetapi jika Anda ingin menginisialisasi bidang ke apa pun selain nilai defaultnya, Anda harus melakukannya dalam deklarasi (2.). Saya pikir itu mungkin penggunaan kata "default" yang membingungkan Anda.
Ricky Helgesson

95
Saya tidak setuju dengan aturan 1 - dengan tidak menentukan nilai default (terlepas dari apakah itu diinisialisasi oleh kompiler atau tidak) Anda meninggalkan pengembang untuk menebak atau mencari dokumentasi tentang nilai default untuk bahasa tertentu itu. Untuk tujuan keterbacaan, saya akan selalu menentukan nilai default.
James

32
Nilai default suatu tipe default(T)selalu nilai yang memiliki representasi biner internal 0.
Olivier Jacot-Descombes

15
Apakah Anda suka aturan 1 atau tidak, itu tidak dapat digunakan dengan bidang hanya baca, yang harus diinisialisasi secara eksplisit pada saat konstruktor selesai.
yoyo

36
Saya tidak setuju dengan mereka yang tidak setuju dengan aturan 1. Tidak apa-apa mengharapkan orang lain untuk belajar bahasa C #. Sama seperti kita tidak mengomentari setiap foreachloop dengan "ini mengulangi yang berikut untuk semua item dalam daftar", kita tidak perlu terus-menerus menyatakan kembali nilai default C #. Kami juga tidak perlu berpura-pura bahwa C # memiliki semantik yang tidak diinisialisasi. Karena tidak adanya nilai memiliki makna yang jelas, tidak masalah untuk mengabaikannya. Jika ideal untuk menjadi eksplisit, Anda harus selalu menggunakan newketika membuat delegasi baru (seperti yang diharuskan dalam C # 1). Tetapi siapa yang melakukan itu? Bahasa ini dirancang untuk coders yang teliti.
Edward Brey

149

Dalam C # itu tidak masalah. Dua contoh kode yang Anda berikan benar-benar setara. Dalam contoh pertama kompiler C # (atau itu CLR?) Akan membangun konstruktor kosong dan menginisialisasi variabel seolah-olah mereka berada di konstruktor (ada sedikit nuansa ini yang dijelaskan Jon Skeet dalam komentar di bawah). Jika sudah ada konstruktor maka inisialisasi "di atas" akan dipindahkan ke atasnya.

Dalam hal praktik terbaik, yang pertama lebih sedikit kesalahan daripada yang terakhir karena seseorang dapat dengan mudah menambahkan konstruktor lain dan lupa untuk rantai itu.


4
Itu tidak benar jika Anda memilih untuk menginisialisasi kelas dengan GetUninitializedObject. Apa pun yang ada di dalam ctor tidak akan disentuh tetapi deklarasi lapangan akan dijalankan.
Wolf5

28
Sebenarnya itu penting. Jika konstruktor kelas dasar memanggil metode virtual (yang umumnya merupakan ide yang buruk, tetapi dapat terjadi) yang ditimpa dalam kelas turunan, maka menggunakan instance initializers variabel, variabel akan diinisialisasi ketika metode dipanggil - sedangkan menggunakan inisialisasi dalam konstruktor, mereka tidak akan. (Inisialisasi variabel instan dieksekusi sebelum konstruktor kelas dasar dipanggil.)
Jon Skeet

2
Betulkah? Saya bersumpah saya mengambil info ini dari Richter's CLR melalui C # (edisi ke-2 saya pikir) dan intinya adalah bahwa ini adalah gula sintaksis (saya mungkin salah baca?) Dan CLR hanya memasukkan variabel ke dalam konstruktor. Tetapi Anda menyatakan bahwa ini bukan masalahnya, yaitu inisialisasi anggota dapat diaktifkan sebelum inisialisasi ctor dalam skenario gila memanggil virtual in base ctor dan memiliki pengesampingan di kelas yang bersangkutan. Apakah saya mengerti dengan benar? Apakah Anda baru tahu ini? Bingung dengan komentar terkini tentang kiriman 5 tahun ini (OMG sudah 5 tahun?).
Quibblesome

@ Quibblesome: Konstruktor kelas anak akan berisi panggilan dirantai ke konstruktor induk. Sebuah kompiler bahasa bebas untuk memasukkan sebanyak atau sesedikit kode sebelum yang diinginkan, asalkan konstruktor induk dipanggil tepat sekali pada semua jalur kode, dan penggunaan terbatas dibuat dari objek yang sedang dibangun sebelum panggilan itu. Salah satu gangguan saya dengan C # adalah bahwa meskipun dapat menghasilkan kode yang menginisialisasi bidang, dan kode yang menggunakan parameter konstruktor, ia tidak menawarkan mekanisme untuk menginisialisasi bidang berdasarkan parameter konstruktor.
supercat

1
@ Quibblesome, yang pertama akan menjadi masalah, jika nilainya ditetapkan dan diteruskan ke metode WCF. Yang akan mengatur ulang data ke tipe data default model. Praktik terbaik adalah melakukan inisialisasi dalam konstruktor (nanti).
Pranesh Janarthanan

16

Semantik C # sedikit berbeda dari Jawa di sini. Dalam C # penugasan dalam deklarasi dilakukan sebelum memanggil konstruktor superclass. Di Jawa dilakukan segera setelah yang memungkinkan 'ini' untuk digunakan (sangat berguna untuk kelas batin anonim), dan berarti bahwa semantik dari dua bentuk benar-benar cocok.

Jika Anda bisa, buat bidang menjadi final.


15

Saya pikir ada satu peringatan. Saya pernah melakukan kesalahan seperti itu: Di dalam kelas turunan, saya mencoba untuk "menginisialisasi pada deklarasi" bidang yang diwarisi dari kelas dasar abstrak. Hasilnya adalah bahwa ada dua set bidang, satu adalah "basis" dan yang lain adalah yang baru dinyatakan, dan saya perlu beberapa waktu untuk debug.

Pelajaran: untuk menginisialisasi bidang yang diwariskan , Anda akan melakukannya di dalam konstruktor.


Jadi, jika Anda merujuk bidang dengan derivedObject.InheritedField, apakah itu merujuk ke basis Anda atau yang diturunkan?
RayLuo

6

Dengan asumsi jenis dalam contoh Anda, pasti lebih suka menginisialisasi bidang dalam konstruktor. Kasus luar biasa adalah:

  • Bidang dalam kelas / metode statis
  • Bidang yang diketik sebagai statis / final / et al

Saya selalu memikirkan daftar bidang di bagian atas kelas sebagai daftar isi (apa yang terkandung di sini, bukan bagaimana itu digunakan), dan konstruktor sebagai pengantar. Metode tentu saja adalah bab.


Mengapa "pasti" begitu? Anda hanya menyediakan preferensi gaya, tanpa menjelaskan alasannya. Oh, tunggu, tidak apa-apa, menurut jawaban @quibblesome , mereka "sama sekali setara", jadi itu benar-benar hanya preferensi gaya pribadi.
RayLuo

4

Bagaimana jika saya katakan, itu tergantung?

Secara umum saya menginisialisasi segalanya dan melakukannya dengan cara yang konsisten. Ya itu terlalu eksplisit tetapi juga sedikit lebih mudah untuk dipertahankan.

Jika kita khawatir tentang kinerja, maka saya hanya menginisialisasi apa yang harus dilakukan dan menempatkannya di area yang memberikan hasil terbaik.

Dalam sistem waktu nyata, saya mempertanyakan apakah saya memerlukan variabel atau konstanta sama sekali.

Dan di C ++ saya sering melakukan inisialisasi tanpa di kedua tempat dan memindahkannya ke fungsi Init (). Mengapa? Nah, di C ++ jika Anda menginisialisasi sesuatu yang bisa melempar pengecualian selama konstruksi objek Anda membuka diri untuk kebocoran memori.


4

Ada banyak dan berbagai situasi.

Saya hanya perlu daftar kosong

Situasinya jelas. Saya hanya perlu menyiapkan daftar saya dan mencegah pengecualian dilemparkan ketika seseorang menambahkan item ke daftar.

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

    public CsvFile()
    {
    }
}

Saya tahu nilainya

Saya tahu persis nilai apa yang ingin saya miliki secara default atau saya perlu menggunakan beberapa logika lain.

public class AdminTeam
{
    private List<string> usernames;

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

atau

public class AdminTeam
{
    private List<string> usernames;

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

Daftar kosong dengan nilai yang mungkin

Terkadang saya mengharapkan daftar kosong secara default dengan kemungkinan menambahkan nilai melalui konstruktor lain.

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

    public AdminTeam()
    {
    }

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

3

Di Jawa, penginisialisasi dengan deklarasi berarti bidang selalu diinisialisasi dengan cara yang sama, terlepas dari konstruktor mana yang digunakan (jika Anda memiliki lebih dari satu) atau parameter konstruktor Anda (jika mereka memiliki argumen), meskipun konstruktor mungkin selanjutnya ubah nilainya (jika belum final). Jadi, menggunakan penginisialisasi dengan deklarasi menunjukkan kepada pembaca bahwa nilai yang diinisialisasi adalah nilai yang dimiliki bidang dalam semua kasus , terlepas dari konstruktor mana yang digunakan dan terlepas dari parameter yang diteruskan ke konstruktor mana pun. Oleh karena itu gunakan penginisialisasi dengan deklarasi hanya jika, dan selalu jika, nilai untuk semua objek yang dibangun adalah sama.


3

Desain C # menyarankan bahwa inisialisasi inline lebih disukai, atau itu tidak akan dalam bahasa. Setiap kali Anda dapat menghindari referensi silang antara berbagai tempat dalam kode, Anda umumnya lebih baik.

Ada juga masalah konsistensi dengan inisialisasi bidang statis, yang harus sesuai untuk kinerja terbaik. Pedoman Kerangka Desain untuk Desain Konstruktor mengatakan ini:

✓ PERTIMBANGKAN inisialisasi bidang statis inline daripada secara eksplisit menggunakan konstruktor statis, karena runtime mampu mengoptimalkan kinerja tipe yang tidak memiliki konstruktor statis yang ditentukan secara eksplisit.

"Pertimbangkan" dalam konteks ini berarti melakukannya kecuali ada alasan kuat untuk tidak melakukannya. Dalam kasus bidang penginisialisasi statis, alasan yang bagus adalah jika inisialisasi terlalu rumit untuk dikodekan secara inline.


2

Bersikap konsisten itu penting, tetapi ini pertanyaan yang harus Anda tanyakan pada diri sendiri: "Apakah saya memiliki konstruktor untuk hal lain?"

Biasanya, saya membuat model untuk transfer data yang kelas itu sendiri tidak melakukan apa-apa selain berfungsi sebagai perumahan untuk variabel.

Dalam skenario ini, saya biasanya tidak memiliki metode atau konstruktor. Rasanya konyol bagi saya untuk membuat konstruktor untuk tujuan eksklusif menginisialisasi daftar saya, terutama karena saya dapat menginisialisasi mereka sesuai dengan deklarasi.

Jadi seperti yang banyak orang katakan, itu tergantung pada penggunaan Anda. Tetap sederhana, dan jangan membuat tambahan apa pun yang tidak harus Anda lakukan.


2

Pertimbangkan situasi di mana Anda memiliki lebih dari satu konstruktor. Akankah inisialisasi berbeda untuk konstruktor yang berbeda? Jika mereka akan sama, lalu mengapa mengulangi untuk setiap konstruktor? Ini sesuai dengan pernyataan kokos, tetapi mungkin tidak terkait dengan parameter. Katakanlah, misalnya, Anda ingin menyimpan bendera yang menunjukkan bagaimana objek itu dibuat. Kemudian bendera itu akan diinisialisasi secara berbeda untuk konstruktor yang berbeda terlepas dari parameter konstruktor. Di sisi lain, jika Anda mengulang inisialisasi yang sama untuk setiap konstruktor Anda meninggalkan kemungkinan bahwa Anda (tidak sengaja) mengubah parameter inisialisasi di beberapa konstruktor tetapi tidak pada yang lain. Jadi, konsep dasar di sini adalah bahwa kode umum harus memiliki lokasi yang sama dan tidak berpotensi diulang di lokasi yang berbeda.


1

Ada sedikit manfaat kinerja untuk menetapkan nilai dalam deklarasi. Jika Anda mengaturnya di konstruktor itu sebenarnya sedang diatur dua kali (pertama ke nilai default, kemudian reset di ctor).


2
Di C #, bidang selalu ditetapkan nilai default terlebih dahulu. Kehadiran penginisialisasi tidak membuat perbedaan.
Jeffrey L Whitledge

0

Saya biasanya mencoba konstruktor untuk melakukan apa-apa selain mendapatkan dependensi dan menginisialisasi anggota contoh terkait dengan mereka. Ini akan membuat hidup Anda lebih mudah jika Anda ingin menguji kelas Anda.

Jika nilai yang akan Anda tetapkan ke variabel instan tidak terpengaruh oleh parameter apa pun yang akan Anda berikan kepada Anda konstruktor, maka tetapkan pada waktu deklarasi.


0

Bukan jawaban langsung untuk pertanyaan Anda tentang praktik terbaik tetapi poin penyegaran penting dan terkait adalah bahwa dalam kasus definisi kelas generik, biarkan di kompiler untuk menginisialisasi dengan nilai default atau kita harus menggunakan metode khusus untuk menginisialisasi bidang ke nilai default mereka (jika itu mutlak diperlukan untuk pembacaan kode).

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

Dan metode khusus untuk menginisialisasi bidang generik ke nilai default adalah sebagai berikut:

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

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

0

Saat Anda tidak memerlukan penanganan logika atau kesalahan:

  • Inisialisasi bidang kelas pada deklarasi

Saat Anda membutuhkan penanganan logika atau kesalahan:

  • Inisialisasi bidang kelas dalam konstruktor

Ini bekerja dengan baik ketika nilai inisialisasi tersedia dan inisialisasi dapat diletakkan pada satu baris. Namun, bentuk inisialisasi ini memiliki keterbatasan karena kesederhanaannya. Jika inisialisasi memerlukan beberapa logika (misalnya, penanganan kesalahan atau loop untuk mengisi array yang kompleks), tugas sederhana tidak memadai. Variabel instan dapat diinisialisasi dalam konstruktor, di mana penanganan kesalahan atau logika lainnya dapat digunakan.

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.