Saya mengumpulkan beberapa kotak sudut dan asah otak dan selalu ingin mendengar lebih banyak. Halaman ini hanya mencakup bit bahasa C # dan bobs, tapi saya juga menemukan inti .NET juga menarik. Sebagai contoh, ini adalah salah satu yang tidak ada di halaman, tetapi menurut saya luar biasa:
string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));
Saya berharap untuk mencetak False - lagipula, "baru" (dengan tipe referensi) selalu membuat objek baru, bukan? Spesifikasi untuk C # dan CLI menunjukkan bahwa seharusnya. Ya, tidak dalam kasus khusus ini. Ini mencetak True, dan telah dilakukan pada setiap versi kerangka kerja yang telah saya uji. (Aku belum mencobanya pada Mono, diakui ...)
Untuk lebih jelasnya, ini hanya contoh dari hal yang saya cari - saya tidak terlalu mencari diskusi / penjelasan tentang keanehan ini. (Ini tidak sama dengan interning string normal; khususnya, string interning tidak biasanya terjadi ketika konstruktor dipanggil.) Saya benar-benar meminta perilaku aneh yang serupa.
Ada permata lain yang bersembunyi di sana?
Jawaban:
Saya pikir saya menunjukkan yang ini sebelumnya, tapi saya suka kesenangannya di sini - ini butuh beberapa debug untuk melacak! (kode asli jelas lebih kompleks dan halus ...)
Jadi apa yang ...
Jawab: apa saja
Nullable<T>
- sepertiint?
. Semua metode diganti, kecuali GetType () yang tidak bisa; jadi itu dilemparkan (kotak) ke objek (dan karenanya ke nol) untuk memanggil objek.GetType () ... yang memanggil null ;-pPerbarui: plot mengental ... Ayende Rahien melemparkan tantangan serupa di blog-nya , tetapi dengan
where T : class, new()
:Tapi itu bisa dikalahkan! Menggunakan tipuan yang sama yang digunakan oleh hal-hal seperti remoting; peringatan - berikut ini adalah kejahatan murni :
Dengan ini di tempat,
new()
panggilan dialihkan ke proksi (MyFunnyProxyAttribute
), yang kembalinull
. Sekarang pergi dan cuci matamu!sumber
Pembulatan Bankir.
Yang ini bukan kompiler bug atau malfungsi, tapi tentu saja kasus sudut yang aneh ...
.Net Framework menggunakan skema atau pembulatan yang dikenal sebagai Pembulatan Bankir.
Dalam Pembulatan Bankir, 0,5 angka dibulatkan ke angka genap terdekat, jadi
Hal ini dapat menyebabkan beberapa bug yang tidak terduga dalam perhitungan keuangan berdasarkan pembulatan Round-Half-Up yang lebih dikenal.
Ini juga berlaku untuk Visual Basic.
sumber
int(fVal + 0.5)
sering melihat bahkan dalam bahasa yang memiliki fungsi pembulatan bawaan.Apa yang akan fungsi ini lakukan jika dipanggil sebagai
Rec(0)
(tidak di bawah debugger)?Menjawab:
Ini karena kompiler JIT 64-bit menerapkan optimisasi panggilan , sedangkan JIT 32-bit tidak.
Sayangnya saya belum punya mesin 64-bit untuk memverifikasi ini, tetapi metode ini tidak memenuhi semua persyaratan untuk optimasi panggilan ekor. Jika ada yang punya, saya akan tertarik untuk melihat apakah itu benar.
sumber
++
sana benar-benar mengusir saya. Tidak bisakah kamu meneleponRec(i + 1)
seperti orang normal?Tetapkan Ini!
Ini adalah salah satu yang ingin saya tanyakan di pesta-pesta (yang mungkin mengapa saya tidak diundang lagi):
Bisakah Anda membuat kompilasi kode berikut?
Cheat yang mudah bisa berupa:
Tetapi solusi sebenarnya adalah ini:
Jadi sedikit fakta bahwa tipe nilai (struct) dapat menetapkan ulang
this
variabel mereka .sumber
//this = new Teaser();
:-)public Foo(int bar){this = new Foo(); specialVar = bar;}
. Ini tidak efisien dan tidak benar-benar dibenarkan (specialVar
ditugaskan dua kali), tetapi hanya FYI. (Itulah alasan yang diberikan dalam buku ini, saya tidak tahu mengapa kita tidak seharusnya melakukannyapublic Foo(int bar) : this()
)Beberapa tahun yang lalu, ketika mengerjakan program loyalitas, kami memiliki masalah dengan jumlah poin yang diberikan kepada pelanggan. Masalah ini terkait dengan casting / convert double ke int.
Dalam kode di bawah ini:
apakah i1 == i2 ?
Ternyata i1! = I2. Karena perbedaan kebijakan pembulatan dalam Konversi dan operator, nilai sebenarnya adalah:
Itu selalu lebih baik untuk memanggil Math.Ceiling () atau Math.Floor () (atau Math.Round dengan MidpointRounding yang memenuhi persyaratan kami)
sumber
Mereka seharusnya membuat 0 bilangan bulat bahkan ketika ada fungsi enum yang berlebihan.
Saya tahu dasar pemikiran tim inti C # untuk memetakan 0 hingga enum, tapi tetap saja, itu tidak sesortogonal sebagaimana seharusnya. Contoh dari Npgsql .
Contoh uji:
sumber
none
yang dapat digunakan dikonversi ke enum apa pun, dan menjadikan 0 selalu int dan tidak secara implisit dikonversi menjadi enum.0
seharusnya dapat dikonversi menjadi enum. Lihat Eric Lippert Blog: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspxIni adalah salah satu yang paling tidak biasa yang pernah saya lihat sejauh ini (selain dari yang di sini tentu saja!):
Ini memungkinkan Anda mendeklarasikannya tetapi tidak memiliki kegunaan nyata, karena ia akan selalu meminta Anda untuk membungkus kelas apa pun yang Anda isi di tengah dengan Turtle lainnya.
[lelucon] Kurasa itu kura-kura sampai habis ... [/ bercanda]
sumber
class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Inilah yang baru saya ketahui tentang baru-baru ini ...
Sekilas di atas terlihat gila, tetapi sebenarnya legal. Tidak , sungguh (walaupun saya telah melewatkan bagian penting, tapi itu bukan sesuatu yang gila seperti "tambahkan kelas yang disebut
IFoo
" atau "tambahkanusing
alias untuk menunjukIFoo
pada sebuah kelas").Lihat apakah Anda dapat mengetahui alasannya, lalu: Siapa bilang Anda tidak bisa membuat instance antarmuka?
sumber
Kapan seorang Boolean tidak Benar atau Salah?
Bill menemukan bahwa Anda dapat meretas boolean sehingga jika A benar dan B benar, (A dan B) salah.
Boolean yang diretas
sumber
Saya datang agak terlambat ke pesta, tapi saya punya
tigaempatlima:Jika Anda polling Invoke Diperlukan pada kontrol yang belum dimuat / ditampilkan, itu akan mengatakan palsu - dan meledak di wajah Anda jika Anda mencoba mengubahnya dari utas lainnya ( solusinya adalah untuk referensi ini. Tangani pencipta kontrol).
Satu lagi yang membuat saya tersandung adalah bahwa diberikan pertemuan dengan:
jika Anda menghitung MyEnum.Red.ToString () di majelis lain, dan di sela waktu seseorang telah mengkompilasi ulang enum Anda ke:
saat runtime, Anda akan mendapatkan "Hitam".
Saya memiliki sebuah majelis bersama dengan beberapa konstanta yang berguna. Pendahulu saya telah meninggalkan banyak properti hanya-mendapatkan yang tampak jelek, saya pikir saya akan menyingkirkan kekacauan dan hanya menggunakan const publik. Saya sangat terkejut ketika VS mengkompilasi nilai-nilai mereka, dan bukan referensi.
Jika Anda menerapkan metode baru antarmuka dari perakitan lain, tetapi Anda membangun kembali referensi versi lama perakitan itu, Anda mendapatkan TypeLoadException (tidak ada implementasi 'Metode Baru'), meskipun Anda telah mengimplementasikannya (lihat di sini ).
Kamus <,>: "Urutan pengembalian barang tidak ditentukan". Ini mengerikan , karena kadang-kadang bisa menggigit Anda, tetapi bekerja pada orang lain, dan jika Anda baru saja secara buta berasumsi bahwa Kamus akan bermain bagus ("mengapa tidak? Saya pikir, List tidak"), Anda benar-benar harus hidung Anda sebelum akhirnya mulai mempertanyakan asumsi Anda.
sumber
VB.NET, nullables dan operator ternary:
Ini membutuhkan waktu untuk melakukan debug, karena saya diharapkan
i
untuk memuatnyaNothing
.Apa yang sebenarnya saya isi?
0
.Ini mengejutkan tetapi sebenarnya perilaku "benar":
Nothing
di VB.NET tidak persis sama dengannull
di CLR:Nothing
bisa berartinull
ataudefault(T)
untuk tipe nilaiT
, tergantung pada konteksnya. Dalam kasus di atas,If
menyimpulkanInteger
sebagai jenis umumNothing
dan5
, jadi, dalam hal ini,Nothing
berarti0
.sumber
Saya menemukan kasus sudut benar-benar aneh yang mengalahkan yang pertama dengan tembakan panjang.
Metode String.Equals (String, String, StringComparison) sebenarnya tidak bebas efek samping.
Saya sedang mengerjakan blok kode yang memiliki ini pada baris dengan sendirinya di bagian atas beberapa fungsi:
Menghapus garis yang mengarah ke stack overflow di tempat lain dalam program ini.
Kode tersebut ternyata menginstal handler untuk apa yang pada dasarnya adalah acara BeforeAssemblyLoad dan coba lakukan
Sekarang aku seharusnya tidak memberitahumu. Menggunakan budaya yang belum pernah digunakan sebelumnya dalam perbandingan string menyebabkan beban perakitan. InvariantCulture bukan pengecualian untuk ini.
sumber
Berikut adalah contoh bagaimana Anda dapat membuat struct yang menyebabkan pesan kesalahan "Mencoba membaca atau menulis memori yang dilindungi. Ini sering merupakan indikasi bahwa memori lain rusak". Perbedaan antara sukses dan gagal sangat halus.
Tes unit berikut menunjukkan masalahnya.
Lihat apakah Anda dapat menemukan kesalahan.
sumber
+= 500
panggilan:ldc.i4 500
(mendorong 500 sebagai Int32), kemudiancall valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)
- sehingga kemudian memperlakukan sebagaidecimal
(96-bit) tanpa konversi apapun. Jika Anda menggunakannya+= 500M
dengan benar. Itu hanya terlihat seperti kompiler berpikir itu dapat melakukannya dengan satu cara (mungkin karena operator int implisit) dan kemudian memutuskan untuk melakukannya dengan cara lain.C # mendukung konversi antara array dan daftar selama array tidak multidimensi dan ada hubungan warisan antara tipe dan tipe adalah tipe referensi
Perhatikan bahwa ini tidak berhasil:
sumber
Ini adalah hal teraneh yang pernah saya alami:
Digunakan sebagai berikut:
Akan melempar
NullReferenceException
. Ternyata beberapa tambahan dikompilasi oleh kompiler C # ke panggilanString.Concat(object[])
. Sebelum .NET 4, ada bug dalam kelebihan Concat di mana objek diperiksa untuk null, tetapi bukan hasil dari ToString ():Ini adalah bug oleh ECMA-334 §14.7.4:
sumber
.ToString
seharusnya tidak pernah mengembalikan null, tetapi string.Empty. Namun demikian dan kesalahan dalam kerangka tersebut.Menarik - ketika saya pertama kali melihat bahwa saya berasumsi itu adalah sesuatu yang sedang diperiksa oleh kompiler C #, tetapi bahkan jika Anda memancarkan IL secara langsung untuk menghilangkan kemungkinan interferensi, hal itu masih terjadi, yang berarti itu adalah
newobj
op-code yang melakukan memeriksa.Ini juga sama dengan
true
jika Anda mengecek terhadapstring.Empty
yang berarti op-code ini harus memiliki perilaku khusus untuk menterjemahkan string kosong.sumber
Outputnya adalah "Mencoba membaca memori yang dilindungi. Ini merupakan indikasi bahwa memori lain rusak."
sumber
PropertyInfo.SetValue () dapat menetapkan ints ke enums, ints ke int nullable, enums ke enums nullable, tetapi tidak ints ke enums nullable.
Deskripsi lengkap di sini
sumber
Bagaimana jika Anda memiliki kelas generik yang memiliki metode yang dapat dibuat ambigu tergantung pada tipe argumen? Saya mengalami situasi ini baru-baru ini menulis kamus dua arah. Saya ingin menulis
Get()
metode simetris yang akan mengembalikan kebalikan dari argumen apa pun yang disahkan. Sesuatu seperti ini:Semua baik-baik saja jika Anda membuat contoh di mana
T1
danT2
berbagai jenis:Tetapi jika
T1
danT2
sama (dan mungkin jika satu adalah subkelas dari yang lain), itu adalah kesalahan kompiler:Menariknya, semua metode lain dalam kasus kedua masih dapat digunakan; itu hanya panggilan ke metode sekarang-ambigu yang menyebabkan kesalahan kompiler. Kasus yang menarik, jika agak tidak mungkin dan tidak jelas.
sumber
C # Puzzler Aksesibilitas
Kelas turunan berikut mengakses bidang pribadi dari kelas dasarnya, dan kompilator diam-diam melihat ke sisi lain:
Bidang ini memang pribadi:
Ingin menebak bagaimana kita dapat membuat kompilasi kode seperti itu?
.
.
.
.
.
.
.
Menjawab
Kuncinya adalah mendeklarasikan
Derived
sebagai kelas dalam dariBase
:Kelas batin diberi akses penuh ke anggota kelas luar. Dalam hal ini kelas batin juga berasal dari kelas luar. Ini memungkinkan kita untuk "mematahkan" enkapsulasi anggota pribadi.
sumber
class A { private int _i; public void foo(A other) { int res = other._i; } }
Baru saja menemukan hal kecil yang menyenangkan hari ini:
Ini melempar kesalahan kompilasi.
Panggilan ke metode 'Inisialisasi' perlu dikirim secara dinamis, tetapi tidak bisa karena itu adalah bagian dari ekspresi akses dasar. Pertimbangkan untuk melemparkan argumen dinamis atau menghilangkan akses basis.
Jika saya menulis base.Initialize (stuff as object); itu bekerja dengan sempurna, namun ini tampaknya menjadi "kata ajaib" di sini, karena ia melakukan hal yang persis sama, semuanya masih diterima sebagai dinamis ...
sumber
Dalam API yang kami gunakan, metode yang mengembalikan objek domain mungkin mengembalikan "objek null" khusus. Dalam implementasi ini, operator perbandingan dan
Equals()
metode diganti untuk kembalitrue
jika dibandingkan dengannull
.Jadi pengguna API ini mungkin memiliki beberapa kode seperti ini:
atau mungkin sedikit lebih verbose, seperti ini:
di mana
GetDefault()
metode mengembalikan nilai default yang ingin kita gunakan alih-alihnull
. Kejutan itu mengejutkan saya ketika saya menggunakan ReSharper dan mengikuti anjurannya untuk menulis ulang salah satu dari ini sebagai berikut:Jika objek pengujian adalah objek nol yang dikembalikan dari API alih-alih sepantasnya
null
, perilaku kode sekarang telah berubah, karena operator penggabungan nol benar-benar memeriksanull
, tidak menjalankanoperator=
atauEquals()
.sumber
Pertimbangkan kasus aneh ini:
Jika
Base
danDerived
dideklarasikan dalam rakitan yang sama, kompiler akan membuatnyaBase::Method
virtual dan disegel (dalam CIL), meskipunBase
tidak mengimplementasikan antarmuka.Jika
Base
danDerived
berada di majelis yang berbeda, saat menyusunDerived
majelis, kompilator tidak akan mengubah majelis lain, sehingga akan memperkenalkan anggota di dalamnyaDerived
yang akan menjadi implementasi eksplisit untukMyInterface::Method
itu hanya akan mendelegasikan panggilan keBase::Method
.Compiler harus melakukan ini untuk mendukung pengiriman polimorfik berkaitan dengan antarmuka, yaitu harus membuat metode itu virtual.
sumber
Berikut ini mungkin pengetahuan umum yang hanya kurang, tapi eh. Beberapa waktu yang lalu, kami memiliki kasus bug yang termasuk properti virtual. Abstraksi konteksnya sedikit, pertimbangkan kode berikut, dan terapkan breakpoint ke area yang ditentukan:
Saat berada dalam
Derived
konteks objek, Anda bisa mendapatkan perilaku yang sama saat menambahkanbase.Property
sebagai arloji, atau mengetikbase.Property
ke arloji cepat.Butuh waktu untuk menyadari apa yang sedang terjadi. Pada akhirnya saya tercerahkan oleh Quickwatch. Saat membuka Quickwatch dan menjelajahi
Derived
objek d (atau dari konteks objek,this
) dan memilih bidangbase
, bidang edit di atas Quickwatch menampilkan pemeran berikut:Yang berarti bahwa jika pangkalan diganti seperti itu, panggilan akan menjadi
untuk Arloji, Quickwatch, dan tooltip debug-mouse, dan kemudian masuk akal untuk menampilkannya
"AWESOME"
daripada"BASE_AWESOME"
mempertimbangkan polimorfisme. Saya masih tidak yakin mengapa itu akan mengubahnya menjadi pemain, satu hipotesis adalah bahwacall
mungkin tidak tersedia dari konteks modul-modul itu, dan hanyacallvirt
.Bagaimanapun, itu jelas tidak mengubah apa pun dalam hal fungsionalitas,
Derived.BaseProperty
masih akan benar-benar kembali"BASE_AWESOME"
, dan dengan demikian ini bukan akar bug kami di tempat kerja, hanya komponen yang membingungkan. Namun saya merasa menarik bagaimana itu bisa menyesatkan para pengembang yang tidak menyadari fakta itu selama sesi debug mereka, khususnya jikaBase
tidak diekspos dalam proyek Anda tetapi lebih dirujuk sebagai DLL pihak ke-3, yang mengakibatkan Devs hanya mengatakan:sumber
Yang ini cukup sulit untuk teratas. Saya berlari ke dalamnya ketika saya mencoba untuk membangun implementasi RealProxy yang benar-benar mendukung Begin / EndInvoke (terima kasih MS karena membuat ini tidak mungkin dilakukan tanpa peretasan yang mengerikan). Contoh ini pada dasarnya adalah bug di CLR, jalur kode yang tidak dikelola untuk BeginInvoke tidak memvalidasi bahwa pesan kembali dari RealProxy.PrivateInvoke (dan penggantian Invoke saya) mengembalikan contoh IAsyncResult. Setelah dikembalikan, CLR menjadi sangat bingung dan kehilangan ide tentang apa yang terjadi, seperti yang ditunjukkan oleh tes di bagian bawah.
Keluaran:
sumber
Saya tidak yakin apakah Anda akan mengatakan ini adalah keanehan Windows Vista / 7 atau. Keanehan Net tetapi saya menggaruk-garuk kepala untuk sementara waktu.
Pada Windows Vista / 7 file tersebut sebenarnya akan ditulis
C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt
sumber
Pernahkah Anda berpikir bahwa kompiler C # dapat menghasilkan CIL yang tidak valid? Jalankan ini dan Anda akan mendapatkan
TypeLoadException
:Saya tidak tahu bagaimana tarifnya di kompiler C # 4.0.
EDIT : ini adalah output dari sistem saya:
sumber
Ada sesuatu yang sangat menarik tentang C #, cara menangani penutupan.
Alih-alih menyalin nilai-nilai variabel tumpukan ke variabel bebas penutupan, ia melakukan sihir preprosesor yang membungkus semua kejadian variabel ke dalam objek dan dengan demikian memindahkannya dari tumpukan - langsung ke tumpukan! :)
Saya kira, itu membuat bahasa C # lebih lengkap secara fungsional (atau lambda-selesai ya)) daripada ML itu sendiri (yang menggunakan nilai tumpukan menyalin AFAIK). F # memiliki fitur itu juga, seperti halnya C #.
Itu membawa banyak kesenangan bagi saya, terima kasih MS guys!
Ini bukan keanehan atau kasus sudut ... tetapi sesuatu yang benar-benar tak terduga dari bahasa VM berbasis stack :)
sumber
Dari pertanyaan yang saya tanyakan belum lama ini:
Operator kondisional tidak dapat memberikan secara implisit?
Diberikan:
Di mana
aBoolValue
ditugaskan Benar atau Salah;Berikut ini tidak akan dikompilasi:
Tetapi ini akan:
Jawaban yang diberikan juga cukup bagus.
sumber
ve not test it I
yakin ini akan berhasil: Byte aByteValue = aBoolValue? (Byte) 1: (Byte) 0; Atau: Byte aByteValue = (Byte) (aBoolValue? 1: 0);1 : 0
sendiri secara implisit akan dilemparkan ke int, bukan Byte.Pelingkupan dalam c # benar-benar aneh pada waktu-waktu tertentu. Izinkan saya memberi Anda satu contoh:
Ini gagal dikompilasi, karena perintah redeclared? Ada beberapa dugaan yang tertarik mengapa ini bekerja seperti itu di utas ini di stackoverflow dan di blog saya .
sumber