Mana yang lebih disukai: Nullable <T> .HasValue atau Nullable <T>! = Null?

437

Saya selalu menggunakan Nullable<>.HasValuekarena saya menyukai semantik. Namun, baru-baru ini saya sedang mengerjakan basis kode orang lain yang sudah ada di mana mereka menggunakannya Nullable<> != nullsecara eksklusif.

Apakah ada alasan untuk menggunakan salah satu dari yang lain, atau itu murni preferensi?

  1. int? a;
    if (a.HasValue)
        // ...

vs.

  1. int? b;
    if (b != null)
        // ...
lc.
sumber
9
Aku bertanya pertanyaan serupa ... punya beberapa jawaban yang baik: stackoverflow.com/questions/633286/...
nailitdown
3
Secara pribadi , saya akan menggunakan HasValuekarena saya pikir kata-kata cenderung lebih mudah dibaca daripada simbol. Semua terserah Anda, dan apa yang cocok dengan gaya Anda saat ini.
Jake Petroules
1
.HasValuelebih masuk akal karena menunjukkan jenis adalah tipe T?daripada jenis yang dapat dibatalkan seperti string.
user3791372

Jawaban:

476

Kompiler menggantikan perbandingan nol dengan panggilan ke HasValue, jadi tidak ada perbedaan nyata. Lakukan saja yang lebih mudah dibaca / lebih masuk akal bagi Anda dan kolega Anda.

Rex M
sumber
86
Saya akan menambahkan bahwa "mana yang lebih konsisten / mengikuti gaya pengkodean yang ada."
Josh Lee
20
Wow. Saya benci gula sintaksis ini. int? x = nullmemberi saya ilusi bahwa instance nullable adalah tipe referensi. Tetapi kenyataannya adalah bahwa Nullable <T> adalah tipe nilai. Rasanya saya akan mendapatkan NullReferenceException untuk melakukan: int? x = null; Use(x.HasValue).
KFL
11
@ KFL Jika gula sintaksis mengganggu Anda, cukup gunakan Nullable<int>saja int?.
Cole Johnson
24
Pada tahap awal pembuatan aplikasi, Anda mungkin berpikir sudah cukup untuk menggunakan tipe nilai nullable untuk menyimpan beberapa data, hanya untuk menyadari setelah beberapa saat bahwa Anda memerlukan kelas yang tepat untuk tujuan Anda. Setelah menulis kode asli untuk dibandingkan dengan nol maka memiliki keuntungan bahwa Anda tidak perlu mencari / mengganti setiap panggilan ke HasValue () dengan perbandingan nol.
Anders
15
Sangat konyol mengeluh tentang bisa mengatur Nullable ke null atau membandingkannya dengan null mengingat itu disebut Nullable . Masalahnya adalah bahwa orang menggabungkan "tipe referensi" dengan "dapat menjadi nol", tetapi itu adalah kebingungan konseptual. Future C # akan memiliki jenis referensi yang tidak dapat dibatalkan.
Jim Balter
48

Saya lebih suka (a != null)agar sintaks cocok dengan jenis referensi.

cbp
sumber
11
Yang tentu saja menyesatkan, karena Nullable<>ini bukan tipe referensi.
Luaan
9
Ya, tetapi kenyataannya biasanya sangat sedikit pada titik Anda nol memeriksa.
cbp
31
Itu hanya menyesatkan ke konsepsi bingung. Menggunakan sintaks yang konsisten untuk dua tipe yang berbeda tidak menyiratkan bahwa mereka adalah tipe yang sama. C # memiliki tipe referensi nullable (semua tipe referensi saat ini nullable, tetapi itu akan berubah di masa depan) dan tipe nilai nullable. Menggunakan sintaks yang konsisten untuk semua jenis nullable masuk akal. Sama sekali tidak menyiratkan bahwa tipe nilai nullable adalah tipe referensi, atau bahwa tipe referensi nullable adalah tipe nilai.
Jim Balter
Saya lebih suka HasValuekarena lebih mudah dibaca daripada!= null
Konrad
Penyandian kode lebih mudah dibaca jika Anda tidak mencampur gaya penulisan kode yang sama. Karena tidak semua tempat memiliki properti .HasValue maka itu membuatnya sejak menggunakan! = Null untuk peningkatan konsistensi. Menurut saya.
ColacX
21

Saya melakukan riset tentang hal ini dengan menggunakan metode yang berbeda untuk menetapkan nilai ke int nullable. Inilah yang terjadi ketika saya melakukan berbagai hal. Harus menjelaskan apa yang terjadi. Perlu diingat: Nullable<something>atau steno something?adalah struct yang tampaknya dikompilasi oleh banyak kompiler agar kita gunakan dengan null seolah-olah itu adalah kelas.
Seperti yang akan Anda lihat di bawah, SomeNullable == nulldan SomeNullable.HasValueakan selalu mengembalikan yang diharapkan benar atau salah. Meskipun tidak diperlihatkan di bawah, SomeNullable == 3valid juga (dengan asumsi SomeNullable adalah an int?).
Sementara SomeNullable.Valuemembuat kita error jika kita ditugaskan nulluntuk SomeNullable. Ini sebenarnya satu-satunya kasus di mana nullables dapat menyebabkan masalah bagi kami, berkat kombinasi operator yang kelebihan beban, kelebihan muatanobject.Equals(obj) metode, dan optimisasi kompiler dan bisnis monyet.

Berikut ini adalah deskripsi dari beberapa kode yang saya jalankan, dan output apa yang dihasilkan oleh label:

int? val = null;
lbl_Val.Text = val.ToString(); //Produced an empty string.
lbl_ValVal.Text = val.Value.ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValEqNull.Text = (val == null).ToString(); //Produced "True" (without the quotes)
lbl_ValNEqNull.Text = (val != null).ToString(); //Produced "False"
lbl_ValHasVal.Text = val.HasValue.ToString(); //Produced "False"
lbl_NValHasVal.Text = (!(val.HasValue)).ToString(); //Produced "True"
lbl_ValValEqNull.Text = (val.Value == null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValValNEqNull.Text = (val.Value != null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")

Oke, mari kita coba metode inisialisasi selanjutnya:

int? val = new int?();
lbl_Val.Text = val.ToString(); //Produced an empty string.
lbl_ValVal.Text = val.Value.ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValEqNull.Text = (val == null).ToString(); //Produced "True" (without the quotes)
lbl_ValNEqNull.Text = (val != null).ToString(); //Produced "False"
lbl_ValHasVal.Text = val.HasValue.ToString(); //Produced "False"
lbl_NValHasVal.Text = (!(val.HasValue)).ToString(); //Produced "True"
lbl_ValValEqNull.Text = (val.Value == null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValValNEqNull.Text = (val.Value != null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")

Semua sama seperti sebelumnya. Perlu diingat bahwa menginisialisasi dengan int? val = new int?(null);, dengan null yang diteruskan ke konstruktor, akan menghasilkan kesalahan waktu COMPILE, karena VALUE objek yang dapat dibatalkan TIDAK BISA nullable. Hanya objek pembungkus itu sendiri yang bisa sama dengan nol.

Demikian juga, kami akan mendapatkan kesalahan waktu kompilasi dari:

int? val = new int?();
val.Value = null;

belum lagi itu val.Valueadalah properti baca-saja, artinya kita bahkan tidak bisa menggunakan sesuatu seperti:

val.Value = 3;

tetapi sekali lagi, operator konversi implisit berlebih polimorf mari kita lakukan:

val = 3;

Tidak perlu khawatir tentang polisom apa pun yang bisa menahan, asalkan berfungsi dengan benar? :)

Perrin Larson
sumber
5
"Perlu diingat: Nullable <something> atau steno sesuatu? Adalah kelas." Ini salah! Nullable <T> adalah sebuah struct. Ini membebani Equals dan == operator untuk mengembalikan true bila dibandingkan dengan nol. Kompiler tidak melakukan pekerjaan mewah untuk perbandingan ini.
andrewjs
1
@andrewjs - Anda benar bahwa itu adalah struct (bukan kelas), tetapi Anda salah bahwa itu membebani operator ==. Jika Anda mengetikkan Nullable<X>VisualStudio 2013 dan F12, Anda akan melihat bahwa itu hanya kelebihan konversi ke dan dari X, dan Equals(object other)metode. Namun, saya pikir operator == menggunakan metode itu secara default, jadi efeknya sama. Sebenarnya saya bermaksud memperbarui jawaban ini atas fakta itu untuk sementara waktu sekarang, tetapi saya malas dan / atau sibuk. Komentar ini harus dilakukan untuk saat ini :)
Perrin Larson
Saya melakukan pemeriksaan cepat melalui ildasme dan Anda benar tentang kompiler melakukan sihir; membandingkan objek <T> Nullable dengan null sebenarnya menerjemahkan panggilan ke HasValue. Menarik!
andrewjs
3
@ andrewjs Sebenarnya, kompiler melakukan banyak pekerjaan untuk mengoptimalkan nullables. Misalnya, jika Anda menetapkan nilai ke jenis nullable, sebenarnya tidak akan menjadi nullable sama sekali (misalnya, int? val = 42; val.GetType() == typeof(int)). Jadi tidak hanya nullable struct yang dapat sama dengan null, itu juga sering tidak nullable sama sekali! : D Dengan cara yang sama, ketika Anda memasukkan nilai nullable, Anda sedang bertinju int, tidak int?- dan ketika nilai int?tersebut tidak memiliki nilai, Anda mendapatkan nullalih-alih nilai nullable kotak. Ini pada dasarnya berarti jarang ada overhead dari menggunakan nullable dengan benar :)
Luaan
1
@ JimBalter Benarkah? Itu sangat menarik. Jadi apa yang dikatakan oleh profiler memori tentang bidang nullable di kelas? Bagaimana Anda mendeklarasikan tipe nilai yang mewarisi dari tipe nilai lain dalam C #? Bagaimana Anda mendeklarasikan tipe nullable Anda sendiri yang berperilaku sama dengan tipe nullable .NET? Sejak kapan Nullsuatu jenis .NET? Bisakah Anda menunjuk ke bagian dalam spesifikasi CLR / C # di mana itu dikatakan? Nullables didefinisikan dengan baik dalam spesifikasi CLR, perilaku mereka bukan "implementasi abstraksi" - itu adalah kontrak . Tetapi jika yang terbaik yang dapat Anda lakukan adalah serangan ad hominem, nikmatilah diri Anda.
Luaan
13

Di VB.Net. JANGAN gunakan "IsNot Nothing" saat Anda dapat menggunakan ".HasValue". Saya baru saja memecahkan "Operasi dapat membuat kestabilan runtime" kesalahan trust Sedang dengan mengganti "IsNot Nothing" dengan ".HasValue" Di satu tempat. Saya tidak begitu mengerti mengapa, tetapi ada sesuatu yang terjadi secara berbeda di kompiler. Saya akan berasumsi bahwa "! = Null" di C # mungkin memiliki masalah yang sama.

Carter Medlin
sumber
8
Saya lebih suka HasValuekarena mudah dibaca. IsNot Nothingbenar-benar ekspresi jelek (karena negasi ganda).
Stefan Steinegger
12
@steffan "IsNot Nothing" bukan negasi ganda. "Tidak ada" itu bukan negatif, itu kuantitas yang terpisah, bahkan di luar bidang pemrograman. "Kuantitas ini bukan apa-apa." secara tata bahasa, sama persis dengan mengatakan "Jumlah ini bukan nol." dan tidak ada yang negatif ganda.
jmbpiano
5
Bukannya saya tidak ingin tidak setuju dengan ketiadaan kebenaran di sini, tapi ayolah sekarang. IsNot Tidak ada yang jelas, baik, terlalu negatif. Mengapa tidak menulis sesuatu yang positif dan jelas seperti HasValue? Ini bukan tes tata bahasa, itu coding, di mana tujuan utamanya adalah kejelasan.
Randy Gamage
3
jmbpiano: Saya setuju itu bukan negasi ganda, tapi negasi tunggal dan itu hampir jelek dan tidak sejelas ekspresi positif sederhana.
Kaveh Hadjari
0

Jika Anda menggunakan LINQ dan ingin menjaga kode Anda pendek, saya sarankan untuk selalu menggunakan !=null

Dan inilah sebabnya:

Bayangkan kita memiliki beberapa kelas Foodengan variabel ganda nullableSomeDouble

public class Foo
{
    public double? SomeDouble;
    //some other properties
}   

Jika di suatu tempat dalam kode kita, kita ingin mendapatkan semua Foo dengan nilai SomeDouble non null dari koleksi Foo (dengan asumsi beberapa foo dalam koleksi bisa juga nol), kita berakhir dengan setidaknya tiga cara untuk menulis fungsi kita (jika kita gunakan C # 6):

public IEnumerable<Foo> GetNonNullFoosWithSomeDoubleValues(IEnumerable<Foo> foos)
{
     return foos.Where(foo => foo?.SomeDouble != null);
     return foos.Where(foo=>foo?.SomeDouble.HasValue); // compile time error
     return foos.Where(foo=>foo?.SomeDouble.HasValue == true); 
     return foos.Where(foo=>foo != null && foo.SomeDouble.HasValue); //if we don't use C#6
}

Dan dalam situasi seperti ini saya sarankan untuk selalu mencari yang lebih pendek

yan yankelevich
sumber
2
Ya, foo?.SomeDouble.HasValueadalah kesalahan waktu kompilasi (bukan "lemparan" dalam terminologi saya) dalam konteks itu karena tipenya adalah bool?, bukan hanya bool. ( .WhereMetode ini menginginkan a Func<Foo, bool>.) Hal ini diperbolehkan untuk dilakukan (foo?.SomeDouble).HasValue, tentu saja, karena itu memiliki tipe bool. Inilah yang baris pertama Anda "diterjemahkan" secara internal oleh kompiler C # (setidaknya secara formal).
Jeppe Stig Nielsen
-6

Jawaban umum dan aturan praktis: jika Anda memiliki pilihan (misalnya menulis serializers khusus) untuk memproses Nullable dalam pipa yang berbeda dari object- dan menggunakan properti spesifik mereka - lakukan dan gunakan properti spesifik Nullable. Jadi dari sudut pandang pemikiran yang konsisten HasValueharus lebih disukai. Pemikiran yang konsisten dapat membantu Anda menulis kode yang lebih baik agar tidak menghabiskan terlalu banyak waktu dalam perincian. Misalnya ada metode kedua akan berkali-kali lebih efektif (kebanyakan karena kompiler inlining dan tinju tetapi angka masih sangat ekspresif):

public static bool CheckObjectImpl(object o)
{
    return o != null;
}

public static bool CheckNullableImpl<T>(T? o) where T: struct
{
    return o.HasValue;
}

Tes patokan:

BenchmarkDotNet=v0.10.5, OS=Windows 10.0.14393
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233539 Hz, Resolution=309.2587 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
  Core   : .NET Core 4.6.25009.03, 64bit RyuJIT


        Method |  Job | Runtime |       Mean |     Error |    StdDev |        Min |        Max |     Median | Rank |  Gen 0 | Allocated |
-------------- |----- |-------- |-----------:|----------:|----------:|-----------:|-----------:|-----------:|-----:|-------:|----------:|
   CheckObject |  Clr |     Clr | 80.6416 ns | 1.1983 ns | 1.0622 ns | 79.5528 ns | 83.0417 ns | 80.1797 ns |    3 | 0.0060 |      24 B |
 CheckNullable |  Clr |     Clr |  0.0029 ns | 0.0088 ns | 0.0082 ns |  0.0000 ns |  0.0315 ns |  0.0000 ns |    1 |      - |       0 B |
   CheckObject | Core |    Core | 77.2614 ns | 0.5703 ns | 0.4763 ns | 76.4205 ns | 77.9400 ns | 77.3586 ns |    2 | 0.0060 |      24 B |
 CheckNullable | Core |    Core |  0.0007 ns | 0.0021 ns | 0.0016 ns |  0.0000 ns |  0.0054 ns |  0.0000 ns |    1 |      - |       0 B |

Kode benchmark:

public class BenchmarkNullableCheck
{
    static int? x = (new Random()).Next();

    public static bool CheckObjectImpl(object o)
    {
        return o != null;
    }

    public static bool CheckNullableImpl<T>(T? o) where T: struct
    {
        return o.HasValue;
    }

    [Benchmark]
    public bool CheckObject()
    {
        return CheckObjectImpl(x);
    }

    [Benchmark]
    public bool CheckNullable()
    {
        return CheckNullableImpl(x);
    }
}

https://github.com/dotnet/BenchmarkDotNet digunakan

PS . Orang mengatakan bahwa saran "lebih memilih HasValue karena pemikiran yang konsisten" tidak berhubungan dan tidak berguna.Bisakah Anda memprediksi kinerja ini?

public static bool CheckNullableGenericImpl<T>(T? t) where T: struct
{
    return t != null; // or t.HasValue?
}

Orang-orang PPS terus minus tetapi tidak ada yang mencoba memprediksi kinerja CheckNullableGenericImpl. Dan ada compiler tidak akan membantu Anda mengganti !=nulldengan HasValue. HasValueharus digunakan secara langsung jika Anda tertarik dengan kinerja.

Roman Pokrovskij
sumber
2
CheckObjectImpl Kotak Anda dapat dibatalkan menjadi object, sedangkan CheckNullableImpltidak menggunakan tinju. Dengan demikian perbandingannya sangat tidak tepat. Bukan hanya itu bukan ongkos, itu juga tidak berguna karena, seperti disebutkan dalam jawaban yang diterima , kompiler menulis ulang !=untuk HasValuetetap.
GSerg
2
Pembaca tidak mengabaikan sifat struct Nullable<T>, Anda melakukannya (dengan meninju menjadi object). Ketika Anda menerapkan != nulldengan nullable di sebelah kiri, tinju tidak terjadi karena dukungan !=untuk nullables bekerja di tingkat kompiler. Ini berbeda ketika Anda menyembunyikan nullable dari kompiler dengan terlebih dahulu memasukkannya ke dalam object. Baik CheckObjectImpl(object o)atau patokan Anda masuk akal pada prinsipnya.
GSerg
3
Masalah saya adalah saya peduli dengan kualitas konten di situs web ini. Apa yang Anda posting adalah menyesatkan atau salah. Jika Anda sedang berusaha untuk menjawab pertanyaan OP, maka jawaban Anda adalah datar keluar salah, yang mudah untuk membuktikan dengan mengganti panggilan untuk CheckObjectImpldengan yang tubuh di dalam CheckObject. Namun komentar terakhir Anda mengungkapkan bahwa Anda sebenarnya memiliki pertanyaan yang sangat berbeda ketika Anda memutuskan untuk menjawab pertanyaan berusia 8 tahun ini, yang membuat jawaban Anda menyesatkan dalam konteks pertanyaan awal. Bukan itu yang ditanyakan OP.
GSerg
3
Tempatkan diri Anda pada posisi pria berikutnya yang googles what is faster != or HasValue. Dia sampai pada pertanyaan ini, menelusuri jawaban Anda, menghargai tolok ukur Anda dan berkata, "Wah, saya tidak akan pernah menggunakan !=karena jelas jauh lebih lambat!" Itu adalah kesimpulan yang sangat salah yang kemudian dia akan lanjutkan menyebar. Itulah mengapa saya percaya jawaban Anda berbahaya - jawaban itu menjawab pertanyaan yang salah dan karenanya menarik kesimpulan yang salah di dalam pembaca yang tidak curiga. Pertimbangkan apa yang terjadi ketika Anda mengubah Anda CheckNullableImpluntuk juga menjadi return o != null;Anda akan mendapatkan hasil benchmark yang sama.
GSerg
8
Saya berdebat dengan jawaban Anda. Jawaban Anda tampak seperti menunjukkan perbedaan antara !=dan HasValuepadahal sebenarnya menunjukkan perbedaan antara object odan T? o. Jika Anda melakukan apa yang saya menyarankan, yaitu, menulis ulang CheckNullableImplsebagai public static bool CheckNullableImpl<T>(T? o) where T: struct { return o != null; }, Anda akan berakhir dengan patokan yang jelas menunjukkan bahwa !=jauh lebih lambat dibandingkan !=. Yang seharusnya mengarahkan Anda pada kesimpulan bahwa masalah yang dijawab oleh jawaban Anda bukan tentang !=vs HasValuesama sekali.
GSerg