Saya telah memperhatikan beberapa perilaku aneh dalam kode saya ketika secara tidak sengaja mengomentari suatu baris dalam suatu fungsi selama tinjauan kode. Sangat sulit untuk mereproduksi tetapi saya akan menggambarkan contoh serupa di sini.
Saya mendapat kelas tes ini:
public class Test
{
public void GetOut(out EmailAddress email)
{
try
{
Foo(email);
}
catch
{
}
}
public void Foo(EmailAddress email)
{
}
}
tidak ada tugas untuk Email GetOut
yang biasanya akan menyebabkan kesalahan:
Parameter email 'out' harus ditetapkan sebelum kontrol meninggalkan metode saat ini
Namun jika EmailAddress berada dalam struct dalam majelis terpisah tidak ada kesalahan yang dibuat dan semuanya mengkompilasi dengan baik.
public struct EmailAddress
{
#region Constructors
public EmailAddress(string email)
: this(email, string.Empty)
{
}
public EmailAddress(string email, string name)
{
this.Email = email;
this.Name = name;
}
#endregion
#region Properties
public string Email { get; private set; }
public string Name { get; private set; }
#endregion
}
Mengapa kompiler tidak memberlakukan bahwa Email harus ditetapkan? Mengapa kode ini dikompilasi jika struct dibuat dalam rakitan yang terpisah, tetapi tidak mengkompilasi jika struct didefinisikan dalam rakitan yang ada?
struct Dog{}
, semuanya baik-baik saja.Jawaban:
TLDR: Ini adalah bug yang dikenal lama. Saya pertama kali menulis tentang hal itu pada tahun 2010:
https://blogs.msdn.microsoft.com/ericlippert/2010/01/18/a-definite-assignment-anomaly/
Ini tidak berbahaya dan Anda dapat dengan aman mengabaikannya, dan memberi selamat pada diri sendiri karena menemukan bug yang agak kabur.
Oh, benar, dengan cara. Itu hanya memiliki ide yang salah tentang kondisi apa yang menyiratkan bahwa variabel pasti ditugaskan, seperti yang akan kita lihat.
Itulah inti dari bug. Bug adalah konsekuensi dari persimpangan bagaimana C # compiler melakukan pengecekan tugas yang pasti pada struct dan bagaimana kompiler memuat metadata dari perpustakaan.
Pertimbangkan ini:
OKE, pada titik ini apa yang kita ketahui?
f
adalah alias untuk variabel tipeFoo
, jadi penyimpanan telah dialokasikan dan jelas setidaknya dalam keadaan keluar dari alokasi penyimpanan. Jika ada nilai yang ditempatkan dalam variabel oleh pemanggil, nilai itu ada di sana.Apa yang kami butuhkan? Kami mengharuskan yang
f
pasti ditugaskan pada setiap titik di mana kontrol berjalanM
normal. Jadi Anda akan mengharapkan sesuatu seperti:yang menetapkan
f.x
danf.y
ke nilai standarnya. Tapi bagaimana dengan ini?Itu juga harus baik-baik saja. Tapi, dan inilah kickernya, mengapa kita perlu menetapkan nilai default hanya untuk melenyapkannya beberapa saat kemudian? Pemeriksa penugasan tugas pasti C # memeriksa untuk melihat apakah setiap bidang ditugaskan! Ini legal:
Dan mengapa itu tidak legal? Ini adalah tipe nilai.
f
adalah variabel, dan sudah berisi nilai jenis yang validFoo
, jadi mari kita atur bidang, dan kita selesai, kan?Baik. Jadi, apa masalahnya?
Bug yang Anda temukan adalah: sebagai penghematan biaya, kompiler C # tidak memuat metadata untuk bidang pribadi struct yang ada di perpustakaan yang dirujuk . Metadata itu bisa sangat besar , dan itu akan memperlambat kompiler untuk kemenangan yang sangat kecil untuk memuat semuanya ke dalam memori setiap waktu.
Dan sekarang Anda harus dapat menyimpulkan penyebab bug yang Anda temukan. Ketika kompilator memeriksa untuk melihat apakah parameter keluar ditetapkan dengan pasti, ia membandingkan jumlah bidang yang diketahui dengan jumlah bidang yang ditentukan diinisialisasi dan dalam kasus Anda hanya tahu tentang bidang publik nol karena metadata bidang pribadi tidak dimuat . Kompilator menyimpulkan "nol bidang wajib diisi, nol bidang diinisialisasi, kami baik."
Seperti yang saya katakan, bug ini telah ada selama lebih dari satu dekade dan orang-orang seperti Anda kadang-kadang menemukan kembali dan melaporkannya. Ini tidak berbahaya, dan tidak mungkin diperbaiki karena memperbaikinya hampir tidak menguntungkan tetapi biaya kinerja yang besar.
Dan tentu saja bug tersebut tidak repro untuk bidang priv dari struct yang ada dalam kode sumber dalam proyek Anda, karena jelas kompiler sudah memiliki informasi tentang bidang pribadi yang ada.
sumber
System.TimeSpan
bukan, kesalahan memang datang:error CS0269: Use of unassigned out parameter 'email'
danerror CS0177: The out parameter 'email' must be assigned to before control leaves the current method
. Hanya ada satu bidang non-statisTimeSpan
, yaitu_ticks
. Iniinternal
untuk mscorlib perakitannya. Apakah majelis ini istimewa? Sama denganSystem.DateTime
, dan bidangnya adalahprivate
Meskipun terlihat seperti bug, itu masuk akal.
'Kesalahan yang hilang' hanya muncul saat menggunakan perpustakaan kelas. Dan perpustakaan kelas mungkin telah ditulis dalam bahasa .net lain, mis. VB.Net. 'Pelacakan penugasan pasti' adalah fitur C #, bukan kerangka kerja.
Jadi secara keseluruhan saya tidak berpikir itu bug tetapi saya tidak tahu tentang pernyataan otoritatif untuk ini.
sumber
default(T)
). Jadi tidak ada pelanggaran keamanan memori atau sesuatu yang serupa.