Jika saya membuat bool dalam kelas saya, hanya sesuatu seperti bool check
, itu default ke false.
Ketika saya membuat bool yang sama dalam metode saya, bool check
(bukan dalam kelas), saya mendapatkan kesalahan "penggunaan pemeriksaan variabel lokal yang belum ditetapkan". Mengapa?
c#
language-design
local-variables
nachime
sumber
sumber
Jawaban:
Jawaban Yuval dan David pada dasarnya benar; menyimpulkan:
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:
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:
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 :
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
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:
Tim desain memilih opsi ketiga.
sumber
bool x;
setara denganbool x = false;
bahkan di dalam suatu metode ?Karena kompiler sedang mencoba untuk mencegah Anda membuat kesalahan.
Apakah menginisialisasi variabel Anda untuk
false
mengubah apa pun di jalur eksekusi khusus ini? Mungkin tidak, mengingatdefault(bool)
itu salah, tetapi memaksa Anda untuk menyadari bahwa ini sedang terjadi. Lingkungan .NET mencegah Anda mengakses "memori sampah", karena akan menginisialisasi nilai apa pun ke default. Tapi tetap saja, bayangkan ini adalah tipe referensi, dan Anda akan meneruskan nilai (nol) yang tidak diinisialisasi ke metode yang mengharapkan non-null, dan mendapatkan NRE saat runtime. Kompiler hanya berusaha mencegahnya, menerima kenyataan bahwa ini kadang-kadang menghasilkanbool b = false
pernyataan.Eric Lippert berbicara tentang ini di sebuah posting blog :
Mengapa ini tidak berlaku untuk bidang kelas? Yah, saya berasumsi garis harus ditarik di suatu tempat, dan inisialisasi variabel lokal jauh lebih mudah untuk didiagnosis dan diperbaiki, dibandingkan dengan bidang kelas. Kompilator dapat melakukan ini, tetapi pikirkan semua kemungkinan pemeriksaan yang perlu dilakukan (di mana beberapa di antaranya tidak tergantung pada kode kelas itu sendiri) untuk mengevaluasi apakah setiap bidang dalam suatu kelas diinisialisasi. Saya bukan perancang kompiler, tetapi saya yakin ini pasti akan lebih sulit karena ada banyak case yang diperhitungkan, dan harus dilakukan tepat waktu juga. Untuk setiap fitur yang Anda rancang, tulis, uji, dan gunakan, dan nilai penerapan ini sebagai lawan dari upaya yang dilakukan akan menjadi tidak layak dan rumit.
sumber
Jawaban singkatnya adalah bahwa kode yang mengakses variabel lokal yang tidak diinisialisasi dapat dideteksi oleh kompiler dengan cara yang dapat diandalkan, menggunakan analisis statis. Padahal ini bukan kasus bidang. Jadi kompiler memberlakukan kasus pertama, tetapi bukan yang kedua.
Ini tidak lebih dari keputusan desain bahasa C #, seperti yang dijelaskan oleh Eric Lippert . CLR dan lingkungan .NET tidak memerlukannya. VB.NET, misalnya, akan mengkompilasi dengan baik dengan variabel lokal yang tidak diinisialisasi, dan pada kenyataannya CLR menginisialisasi semua variabel yang tidak diinisialisasi ke nilai default.
Hal yang sama dapat terjadi dengan C #, tetapi desainer bahasa memilih untuk tidak melakukannya. Alasannya adalah bahwa variabel yang diinisialisasi adalah sumber bug yang sangat besar dan dengan mandat inisialisasi, kompiler membantu mengurangi kesalahan yang tidak disengaja.
Jadi mengapa inisialisasi eksplisit wajib ini tidak terjadi dengan bidang dalam kelas? Hanya karena inisialisasi eksplisit dapat terjadi selama konstruksi, melalui properti yang dipanggil oleh penginisialisasi objek, atau bahkan dengan metode yang dipanggil lama setelah acara. Kompiler tidak dapat menggunakan analisis statis untuk menentukan apakah setiap jalur yang mungkin melalui kode mengarah ke variabel yang diinisialisasi secara eksplisit sebelum kita. Melakukan kesalahan akan mengganggu, karena pengembang dapat dibiarkan dengan kode yang valid yang tidak dapat dikompilasi. Jadi C # tidak memberlakukan sama sekali dan CLR dibiarkan untuk secara otomatis menginisialisasi bidang ke nilai default jika tidak ditetapkan secara eksplisit.
Penegakan C # terhadap inisialisasi variabel lokal terbatas, yang sering menarik pengembang keluar. Pertimbangkan empat baris kode berikut:
Baris kedua kode tidak akan dikompilasi, karena mencoba membaca variabel string yang tidak diinisialisasi. Baris keempat mengkompilasi kode baik-baik saja, seperti yang
array
telah diinisialisasi, tetapi hanya dengan nilai default. Karena nilai default sebuah string adalah nol, kami mendapatkan pengecualian pada saat run-time. Siapa pun yang menghabiskan waktu di sini di Stack Overflow akan tahu bahwa inkonsistensi inisialisasi eksplisit / implisit ini mengarah ke banyak sekali "Mengapa saya mendapatkan" referensi objek yang tidak disetel ke instance objek "kesalahan?" pertanyaan.sumber
public interface I1 { string str {get;set;} }
dan metodeint f(I1 value) { return value.str.Length; }
. Jika ini ada di pustaka, kompiler tidak bisa tahu apa yang akan ditautkan dengan pustaka tersebut, sehingga apakahset
kehendak telah dipanggil sebelumget
, Bidang dukungan mungkin tidak diinisialisasi secara eksplisit, tetapi harus mengkompilasi kode tersebut.f
. Ini akan dihasilkan saat kompilasi konstruktor. Jika Anda meninggalkan konstruktor dengan bidang yang mungkin tidak diinisialisasi, itu akan menjadi kesalahan. Mungkin juga harus ada pembatasan dalam memanggil metode kelas dan getter sebelum semua bidang diinisialisasi.Jawaban yang bagus di atas, tapi saya pikir saya akan memposting jawaban yang jauh lebih sederhana / lebih pendek bagi orang-orang yang malas membaca yang panjang (seperti saya).
Kelas
Properti
Boo
mungkin atau mungkin belum diinisialisasi dalam konstruktor. Jadi ketika menemukanreturn Boo;
itu tidak menganggap bahwa itu sudah diinisialisasi. Itu hanya menekan kesalahan.Fungsi
The
{ }
karakter menentukan ruang lingkup blok kode. Kompilator berjalan di cabang-cabang{ }
blok ini melacak hal-hal. Dapat dengan mudah mengatakan bahwaBoo
itu tidak diinisialisasi. Kesalahan kemudian dipicu.Mengapa ada kesalahan?
Kesalahan diperkenalkan untuk mengurangi jumlah baris kode yang diperlukan untuk membuat kode sumber aman. Tanpa kesalahan di atas akan terlihat seperti ini.
Dari manual:
Referensi: https://msdn.microsoft.com/en-us/library/4y7h161d.aspx
sumber