Jawaban Yuval dan David pada dasarnya benar; menyimpulkan:
- Penggunaan variabel lokal yang tidak ditetapkan kemungkinan merupakan bug, dan ini dapat dideteksi oleh kompiler dengan biaya rendah.
- Penggunaan bidang atau elemen larik yang tidak ditetapkan cenderung lebih kecil bugnya, dan lebih sulit untuk mendeteksi kondisi di kompiler. Oleh karena itu kompiler tidak berusaha mendeteksi penggunaan variabel yang tidak diinisialisasi untuk bidang, dan sebagai gantinya bergantung pada inisialisasi ke nilai default untuk membuat perilaku program menjadi deterministik.
Seorang komentator untuk jawaban David bertanya mengapa tidak mungkin mendeteksi penggunaan bidang yang tidak ditetapkan melalui analisis statis; inilah poin yang ingin saya kembangkan dalam jawaban ini.
Pertama, untuk variabel apa pun, lokal atau tidak, dalam praktiknya tidak mungkin untuk menentukan dengan tepat apakah suatu variabel ditetapkan atau tidak ditetapkan. Mempertimbangkan:
bool x;
if (M()) x = true;
Console.WriteLine(x);
Pertanyaan "apakah x ditugaskan?" setara dengan "apakah M () mengembalikan true?" Sekarang, misalkan M () mengembalikan true jika Teorema Terakhir Fermat berlaku untuk semua bilangan bulat kurang dari sebelas gajillion, dan salah jika sebaliknya. Untuk menentukan apakah x sudah ditentukan, kompiler pada dasarnya harus menghasilkan bukti dari Teorema Terakhir Fermat. Kompilernya tidak sepintar itu.
Jadi apa yang dilakukan oleh kompiler untuk penduduk lokal adalah mengimplementasikan algoritma yang cepat , dan melebih - lebihkan ketika lokal tidak ditentukan secara pasti. Artinya, ia memiliki beberapa positif palsu, di mana dikatakan "Saya tidak dapat membuktikan bahwa lokal ini ditugaskan" meskipun Anda dan saya tahu itu. Sebagai contoh:
bool x;
if (N() * 0 == 0) x = true;
Console.WriteLine(x);
Misalkan N () mengembalikan bilangan bulat. Anda dan saya tahu bahwa N () * 0 akan menjadi 0, tetapi kompiler tidak tahu itu. (Catatan: kompiler C # 2.0 memang tahu itu, tapi saya menghapus optimasi itu, karena spesifikasinya tidak mengatakan bahwa kompiler tahu itu.)
Baiklah, jadi apa yang kita ketahui sejauh ini? Sangat tidak praktis bagi penduduk setempat untuk mendapatkan jawaban yang tepat, tetapi kami dapat memperkirakan terlalu tinggi tentang tidak-ditugaskan-murah dan mendapatkan hasil yang cukup bagus yang salah kaprah di sisi "membuat Anda memperbaiki program yang tidak jelas". Itu bagus. Mengapa tidak melakukan hal yang sama untuk bidang? Artinya, membuat pemeriksa tugas yang pasti yang menaksir terlalu murah?
Nah, berapa banyak cara yang ada untuk lokal diinisialisasi? Itu dapat ditugaskan dalam teks metode. Itu dapat ditugaskan dalam lambda dalam teks metode; bahwa lambda mungkin tidak pernah dipanggil, jadi tugas-tugas itu tidak relevan. Atau dapat diteruskan sebagai "keluar" ke metode lain, pada titik mana kita dapat menganggap itu ditugaskan ketika metode kembali secara normal. Itu adalah poin yang sangat jelas di mana lokal ditugaskan, dan mereka ada di sana dalam metode yang sama dengan yang dinyatakan lokal . Menentukan penugasan yang pasti untuk penduduk setempat hanya membutuhkan analisis lokal . Metode cenderung pendek - jauh lebih sedikit dari sejuta baris kode dalam suatu metode - dan karenanya menganalisis keseluruhan metode cukup cepat.
Sekarang bagaimana dengan bidang? Bidang dapat diinisialisasi dalam konstruktor tentu saja. Atau inisialisasi bidang. Atau konstruktor dapat memanggil metode contoh yang menginisialisasi bidang. Atau konstruktor dapat memanggil metode virtual yang menginisialisasi bidang. Atau konstruktor dapat memanggil metode di kelas lain , yang mungkin di perpustakaan , yang menginisialisasi bidang. Bidang statis dapat diinisialisasi dalam konstruktor statis. Bidang statis dapat diinisialisasi oleh konstruktor statis lainnya .
Pada dasarnya, penginisialisasi untuk bidang bisa berada di mana saja di seluruh program , termasuk di dalam metode virtual yang akan dideklarasikan di perpustakaan yang belum ditulis :
// Library written by BarCorp
public abstract class Bar
{
// Derived class is responsible for initializing x.
protected int x;
protected abstract void InitializeX();
public void M()
{
InitializeX();
Console.WriteLine(x);
}
}
Apakah ada kesalahan saat mengkompilasi pustaka ini? Jika ya, bagaimana cara BarCorp memperbaiki bug? Dengan menetapkan nilai default ke x? Tapi itu yang sudah dilakukan kompiler.
Misalkan perpustakaan ini legal. Jika FooCorp menulis
public class Foo : Bar
{
protected override void InitializeX() { }
}
apakah itu sebuah kesalahan? Bagaimana kompiler seharusnya mengetahui hal itu? Satu-satunya cara adalah dengan melakukan analisis seluruh program yang melacak inisialisasi statis setiap bidang pada setiap jalur yang mungkin melalui program , termasuk jalur yang melibatkan pilihan metode virtual saat runtime . Masalah ini bisa sangat sulit ; itu bisa melibatkan pelaksanaan simulasi jutaan jalur kontrol. Menganalisis aliran kontrol lokal membutuhkan mikrodetik dan tergantung pada ukuran metode. Menganalisis arus kendali global dapat memakan waktu berjam-jam karena itu tergantung pada kompleksitas setiap metode dalam program dan semua perpustakaan .
Jadi mengapa tidak melakukan analisis yang lebih murah yang tidak perlu menganalisis keseluruhan program, dan hanya melebih-lebihkan bahkan lebih parah? Nah, usulkan algoritma yang berfungsi yang tidak membuatnya terlalu sulit untuk menulis program yang benar yang benar-benar dikompilasi, dan tim desain dapat mempertimbangkannya. Saya tidak tahu ada algoritma seperti itu.
Sekarang, komentator menyarankan "mengharuskan konstruktor menginisialisasi semua bidang". Itu bukan ide yang buruk. Bahkan, itu adalah ide yang tidak buruk bahwa C # sudah memiliki fitur itu untuk struct . Dibutuhkan konstruktor struct untuk menetapkan semua bidang pada saat ctor kembali secara normal; konstruktor default menginisialisasi semua bidang ke nilai default.
Bagaimana dengan kelas? Nah, bagaimana Anda tahu bahwa konstruktor telah menginisialisasi bidang ? Ctor dapat memanggil metode virtual untuk menginisialisasi bidang, dan sekarang kita kembali pada posisi yang sama seperti sebelumnya. Structs tidak memiliki kelas turunan; kelas mungkin. Apakah perpustakaan yang berisi kelas abstrak diperlukan untuk berisi konstruktor yang menginisialisasi semua bidangnya? Bagaimana kelas abstrak tahu nilai apa yang harus diinisialisasi bidang?
John menyarankan hanya melarang metode panggilan dalam aktor sebelum kolom diinisialisasi. Jadi, kesimpulannya, opsi kami adalah:
- Jadikan idiom pemrograman yang umum, aman, dan sering digunakan ilegal.
- Lakukan analisis seluruh program yang mahal yang membuat kompilasi membutuhkan waktu berjam-jam untuk mencari bug yang mungkin tidak ada.
- Mengandalkan inisialisasi otomatis ke nilai default.
Tim desain memilih opsi ketiga.