Mengapa ada perbedaan dalam memeriksa null terhadap nilai di VB.NET dan C #?

110

Di VB.NET ini terjadi:

Dim x As System.Nullable(Of Decimal) = Nothing
Dim y As System.Nullable(Of Decimal) = Nothing

y = 5
If x <> y Then
    Console.WriteLine("true")
Else
    Console.WriteLine("false") '' <-- I got this. Why?
End If

Tetapi di C # ini terjadi:

decimal? x = default(decimal?);
decimal? y = default(decimal?);

y = 5;
if (x != y)
{
    Debug.WriteLine("true"); // <-- I got this -- I'm with you, C# :)
}
else
{
    Debug.WriteLine("false");
}

Mengapa ada perbedaan?

buta
sumber
22
itu menakutkan.
Mikeb
8
Saya yakin default(decimal?)mengembalikan 0, bukan null.
Ryan Frame
7
@RyanName NO. Karena ini adalah tipe nullable , ia mengembalikannull
Soner Gönül
4
Oh iya… benar… dalam VB Ifconditional tidak perlu dievaluasi sebagai boolean … uuuugh EDIT: Jadi Nothing <> Anything = Nothingyang berakibat pada Ifpengambilan negative / else route.
Chris Sinclair
13
@JMK: Null, Nothing, dan Empty sebenarnya sedikit berbeda. Jika semuanya sama maka Anda tidak membutuhkan mereka bertiga.
Eric Lippert

Jawaban:

88

VB.NET dan C # .NET adalah bahasa yang berbeda, dibuat oleh tim berbeda yang membuat asumsi berbeda tentang penggunaan; dalam hal ini semantik perbandingan NULL.

Preferensi pribadi saya adalah untuk semantik VB.NET, yang pada dasarnya memberi NULL semantik "Saya belum tahu". Kemudian perbandingan 5 dengan "Saya belum tahu". secara alami adalah "Saya belum tahu"; yaitu NULL. Ini memiliki keuntungan tambahan untuk mencerminkan perilaku NULL di (sebagian besar jika tidak semua) database SQL. Ini juga merupakan interpretasi yang lebih standar (daripada C #) dari logika tiga nilai, seperti yang dijelaskan di sini .

Tim C # membuat asumsi berbeda tentang arti NULL, yang mengakibatkan perbedaan perilaku yang Anda tunjukkan. Eric Lippert menulis blog tentang arti NULL dalam C # . Menurut Eric Lippert: "Saya juga menulis tentang semantik nulls di VB / VBScript dan JScript di sini dan di sini ".

Dalam lingkungan apa pun di mana nilai NULL dimungkinkan, penting untuk mengakui bahwa Law of the Excluded Middle (yaitu bahwa A atau ~ A secara tautologis benar) tidak lagi dapat diandalkan.

Memperbarui:

A bool(sebagai lawan a bool?) hanya dapat mengambil nilai TRUE dan FALSE. Namun implementasi bahasa NULL harus memutuskan bagaimana NULL menyebar melalui ekspresi. Dalam VB ekspresi 5=nulldan 5<>nullKEDUA mengembalikan false. Di C #, dari ekspresi yang sebanding 5==nulldan 5!=nullhanya yang kedua pertama [diperbarui 2014-03-02 - PG] mengembalikan false. Namun, di SETIAP lingkungan yang mendukung null, adalah kewajiban programmer untuk mengetahui tabel kebenaran dan propagasi null yang digunakan oleh bahasa tersebut.

Memperbarui

Artikel blog Eric Lippert (disebutkan dalam komentarnya di bawah) tentang semantik sekarang ada di:

Pieter Geerkens
sumber
4
Terima kasih untuk tautannya. Saya juga menulis tentang semantik nulls di VB / VBScript dan JScript di sini: blogs.msdn.com/b/ericlippert/archive/2003/09/30/53120.aspx dan di sini: blogs.msdn.com/b/ericlippert/ archive / 2003/10/01 / 53128.aspx
Eric Lippert
27
Dan FYI keputusan untuk membuat C # tidak sesuai dengan VB dengan cara ini adalah salah satu yang kontroversial. Saya tidak berada di tim desain bahasa pada saat itu tetapi jumlah debat yang masuk ke dalam keputusan ini cukup besar.
Eric Lippert
2
@ BlueRaja-DannyPflughoeft Dalam C # booltidak boleh memiliki 3 nilai, hanya dua. Itu bool?bisa memiliki tiga nilai. operator ==dan operator !=keduanya kembali bool, tidak bool?, terlepas dari jenis operannya. Selain itu, ifpernyataan hanya dapat menerima a bool, bukan bool?.
Pelayanan
1
Dalam C # ekspresi 5=nulldan 5<>nulltidak valid. Dan dari 5 == nulldan 5 != null, apakah Anda yakin ini adalah detik yang kembali false?
Ben Voigt
1
@BenVoigt: Terima kasih. Semua suara positif itu dan Anda yang pertama melihat kesalahan ketik itu. ;-)
Pieter Geerkens
37

Karena x <> ykembali, Nothingbukan true. Itu tidak didefinisikan karena xtidak didefinisikan. (mirip dengan SQL null).

Catatan: VB.NET Nothing<> C # null.

Anda juga harus membandingkan nilai a Nullable(Of Decimal)hanya jika memiliki nilai.

Jadi VB.NET di atas mirip dengan ini (yang terlihat kurang salah):

If x.HasValue AndAlso y.HasValue AndAlso x <> y Then
    Console.WriteLine("true")
Else
    Console.WriteLine("false")  
End If

Spesifikasi bahasa VB.NET :

7.1.1 Tipe Nilai Nullable ... Tipe nilai nullable dapat berisi nilai yang sama seperti versi tipe non-nullable serta nilai null. Jadi, untuk tipe nilai nullable, menugaskan Nothing ke variabel tipe menetapkan nilai variabel ke nilai null, bukan nilai nol dari tipe nilai.

Sebagai contoh:

Dim x As Integer = Nothing
Dim y As Integer? = Nothing

Console.WriteLine(x) ' Prints zero '
Console.WriteLine(y) ' Prints nothing (because the value of y is the null value) '
Tim Schmelter
sumber
16
"VB.NET Tidak ada <> C # null" apakah itu mengembalikan true untuk C # dan false untuk VB.Net? Bercanda :-p
ken2k
17

Lihatlah CIL yang dihasilkan (saya telah mengubah keduanya menjadi C #):

C #:

private static void Main(string[] args)
{
    decimal? x = null;
    decimal? y = null;
    y = 5M;
    decimal? CS$0$0000 = x;
    decimal? CS$0$0001 = y;
    if ((CS$0$0000.GetValueOrDefault() != CS$0$0001.GetValueOrDefault()) ||
        (CS$0$0000.HasValue != CS$0$0001.HasValue))
    {
        Console.WriteLine("true");
    }
    else
    {
        Console.WriteLine("false");
    }
}

Dasar visual:

[STAThread]
public static void Main()
{
    decimal? x = null;
    decimal? y = null;
    y = 5M;
    bool? VB$LW$t_struct$S3 = new bool?(decimal.Compare(x.GetValueOrDefault(), y.GetValueOrDefault()) != 0);
    bool? VB$LW$t_struct$S1 = (x.HasValue & y.HasValue) ? VB$LW$t_struct$S3 : null;
    if (VB$LW$t_struct$S1.GetValueOrDefault())
    {
        Console.WriteLine("true");
    }
    else
    {
        Console.WriteLine("false");
    }
}

Anda akan melihat bahwa perbandingan dalam Visual Basic mengembalikan Nullable <bool> (bukan bool, false atau true!). Dan undefined dikonversi ke bool adalah false.

Nothingdibandingkan dengan apa pun yang selalu Nothing, tidak salah dalam Visual Basic (sama seperti di SQL).

nothrow
sumber
Mengapa menjawab pertanyaan dengan coba-coba? Harus memungkinkan untuk melakukannya dari spesifikasi bahasa.
David Heffernan
3
@DavidHeffernan, karena ini menunjukkan perbedaan bahasa yang cukup jelas.
nothrow
2
@Yossarian Anda merasa spesifikasi bahasanya tidak jelas tentang masalah ini. Saya tidak setuju. IL adalah detail implementasi yang dapat berubah; spesifikasinya tidak.
Pelayanan
2
@DavidHeffernan: Saya suka sikap Anda dan mendorong Anda untuk mencoba. Spesifikasi bahasa VB terkadang sulit diurai. Lucian telah memperbaikinya selama beberapa tahun sekarang tetapi masih cukup sulit untuk menjelaskan arti sebenarnya dari jenis kasus sudut ini. Saya menyarankan agar Anda mendapatkan salinan spesifikasi, melakukan penelitian, dan melaporkan temuan Anda.
Eric Lippert
2
@Yossarian Hasil mengeksekusi kode IL yang Anda berikan tidak tunduk pada perubahan, tapi itu kode C # / VB yang disediakan akan dikompilasi ke dalam kode IL Anda menunjukkan adalah tunduk pada perubahan (selama perilaku yang IL adalah juga sejalan dengan definisi spesifikasi bahasa).
Pelayanan
6

Masalah yang diamati di sini adalah kasus khusus dari masalah yang lebih umum, yaitu jumlah definisi berbeda tentang persamaan yang mungkin berguna setidaknya dalam beberapa keadaan melebihi jumlah sarana yang umumnya tersedia untuk mengungkapkannya. Masalah ini dalam beberapa kasus diperburuk oleh keyakinan yang tidak menguntungkan bahwa membingungkan untuk memiliki cara berbeda untuk menguji kesetaraan menghasilkan hasil yang berbeda, dan kebingungan seperti itu dapat dihindari dengan memiliki bentuk persamaan yang berbeda menghasilkan hasil yang sama bila memungkinkan.

Pada kenyataannya, penyebab mendasar dari kebingungan adalah kepercayaan yang salah arah bahwa berbagai bentuk pengujian kesetaraan dan ketidaksetaraan diharapkan dapat memberikan hasil yang sama, terlepas dari kenyataan bahwa semantik yang berbeda berguna dalam keadaan yang berbeda. Misalnya, dari sudut pandang aritmatika, sangat berguna untuk dapat memiliki Decimalyang hanya berbeda dalam jumlah angka nol tertinggal yang dibandingkan sebagai sama. Begitu juga untuk doublenilai seperti nol positif dan nol negatif. Di sisi lain, dari sudut pandang caching atau interning, semantik semacam itu bisa mematikan. Misalkan, misalnya, seseorang memiliki suatu Dictionary<Decimal, String>hal yang myDict[someDecimal]seharusnya samasomeDecimal.ToString() . Objek seperti itu akan tampak masuk akal jika ada banyakDecimalnilai yang ingin diubah menjadi string dan diharapkan ada banyak duplikat. Sayangnya, jika digunakan caching seperti itu untuk mengubah 12,3 m dan 12,40 m, diikuti oleh 12,30 m dan 12,4 m, nilai terakhir akan menghasilkan "12.3", dan "12.4", bukan "12.30" dan "12.4".

Kembali ke masalah yang ada, ada lebih dari satu cara yang masuk akal untuk membandingkan objek nullable untuk persamaan. C # mengambil sudut pandang bahwa ==operatornya harus mencerminkan perilakunya Equals. VB.NET mengambil sudut pandang bahwa perilakunya harus mencerminkan beberapa bahasa lain, karena siapa pun yang menginginkan Equalsperilakunya dapat menggunakan Equals. Dalam beberapa hal, solusi yang tepat adalah memiliki konstruksi "jika" tiga arah, dan mengharuskan jika ekspresi kondisional mengembalikan hasil bernilai tiga, kode harus menentukan apa yang harus terjadi dinull kasus tersebut. Karena itu bukan pilihan untuk bahasa apa adanya, alternatif terbaik berikutnya adalah mempelajari cara kerja bahasa yang berbeda dan mengenali bahwa keduanya tidak sama.

Secara kebetulan, operator "Is" Visual Basic, yang kurang dalam C, dapat digunakan untuk menguji apakah objek nullable, sebenarnya, null. Meskipun orang mungkin secara wajar mempertanyakan apakah suatu ifpengujian harus menerima a Boolean?, meminta operator pembanding normal mengembalikan Boolean?daripada Booleansaat dipanggil pada tipe nullable adalah fitur yang berguna. Kebetulan, di VB.NET, jika seseorang mencoba menggunakan operator persamaan daripada Is, ia akan mendapat peringatan bahwa hasil perbandingan akan selaluNothing , dan seseorang harus menggunakan Isjika ingin menguji apakah ada sesuatu yang nol.

supercat
sumber
Menguji apakah kelas adalah null di C # dilakukan oleh == null. Dan menguji apakah tipe nilai nullable memiliki nilai dilakukan oleh .hasValue. Apa gunanya Is Nothingoperator? C # memang memiliki istetapi menguji kompatibilitas tipe. Sehubungan dengan ini, saya benar-benar tidak yakin apa yang coba dikatakan paragraf terakhir Anda.
ErikE
@ErikE: Baik vb.net dan C # memungkinkan jenis nullable diperiksa nilainya menggunakan perbandingan null, meskipun kedua bahasa memperlakukannya sebagai gula sintaksis untuk HasValuepemeriksaan, setidaknya dalam kasus di mana jenisnya diketahui (saya tidak yakin kode apa yang dihasilkan untuk obat generik).
supercat
Dalam obat generik, Anda bisa mendapatkan masalah rumit seputar jenis nullable dan resolusi overload ...
ErikE
3

Semoga postingan ini membantu Anda:

Jika saya ingat dengan benar, 'Nothing' dalam VB berarti "nilai default". Untuk tipe nilai, itulah nilai defaultnya, untuk tipe referensi, nilainya null. Jadi, tidak menugaskan apa pun ke struct, tidak masalah sama sekali.

evgenyl.dll
sumber
3
Ini tidak menjawab pertanyaan itu.
David Heffernan
Tidak, itu tidak menjelaskan apa-apa. Pertanyaannya adalah tentang <>operator di VB, dan bagaimana operasinya pada tipe nullable.
David Heffernan
2

Ini adalah keanehan yang pasti dari VB.

Di VB, jika Anda ingin membandingkan dua tipe nullable, Anda harus menggunakan Nullable.Equals().

Dalam contoh Anda, seharusnya:

Dim x As System.Nullable(Of Decimal) = Nothing
Dim y As System.Nullable(Of Decimal) = Nothing

y = 5
If Not Nullable.Equals(x, y) Then
    Console.WriteLine("true")
Else
    Console.WriteLine("false")
End If
Matthew Watson
sumber
5
Ini adalah "keanehan" jika tidak familiar. Lihat jawaban yang diberikan oleh Pieter Geerkens.
rskar
Saya juga merasa aneh bahwa VB tidak mereproduksi perilaku Nullable<>.Equals(). Seseorang mungkin mengharapkannya untuk bekerja dengan cara yang sama (seperti yang dilakukan C #).
Matthew Watson
Harapan, seperti dalam apa "yang mungkin diharapkan", adalah tentang apa yang pernah dialami. C # dirancang dengan harapan pengguna Java. Java dirancang dengan harapan pengguna C / C ++. Baik atau buruk, VB.NET dirancang dengan harapan pengguna VB6. Lebih banyak makanan untuk dipikirkan di stackoverflow.com/questions/14837209/… dan stackoverflow.com/questions/10176737/…
rskar
1
@MatthewWatson Definisi Nullabletidak ada di versi pertama .NET, itu dibuat setelah C # dan VB.NET telah keluar untuk beberapa waktu dan sudah menentukan perilaku propagasi null mereka. Apakah Anda benar-benar mengharapkan bahasa tersebut konsisten dengan jenis yang tidak akan dibuat selama beberapa tahun? Dari sudut pandang programmer VB.NET, ini adalah Nullable. Sama yang tidak konsisten dengan bahasanya, bukan sebaliknya. (Mengingat bahwa C # dan VB keduanya menggunakan Nullabledefinisi yang sama , tidak mungkin itu konsisten dengan kedua bahasa.)
Servy
0

Kode VB Anda salah - jika Anda mengubah "x <> y" menjadi "x = y", Anda masih akan mendapatkan "false" sebagai hasilnya. Cara paling umum untuk mengungkapkan ini untuk instance nullable adalah "Not x.Equals (y)", dan ini akan menghasilkan perilaku yang sama seperti "x! = Y" di C #.

Dave Doknjas
sumber
1
Kecuali xadalah nothing, dalam hal x.Equals(y)akan melemparkan sebuah pengecualian.
Pelayanan
@Servy: Tersandung pada ini lagi (bertahun-tahun kemudian), dan memperhatikan bahwa saya tidak mengoreksi Anda - "x.Equals (y)" tidak akan mengeluarkan pengecualian untuk instance jenis nullable 'x'. Tipe nullable diperlakukan berbeda oleh compiler.
Dave Doknjas
Secara khusus, instance nullable yang diinisialisasi ke 'null' sebenarnya bukan variabel yang disetel ke null, tetapi instance System.Nullable tanpa nilai yang ditetapkan.
Dave Doknjas