我一直使用Nullable<>.HasValue
由於我喜歡這種語義。 可是,最近我正在開發其餘人的現有代碼庫,其中他們專門使用Nullable<> != null
。 git
是否有理由優先使用一個,仍是純粹是偏心? github
int? a; if (a.HasValue) // ...
與 dom
int? b; if (b != null) // ...
我更喜歡(a != null)
以便語法與引用類型匹配。 函數
我經過使用不一樣的方法將值分配給可爲null的int進行了一些研究。 這是我作各類事情時發生的事情。 應該澄清發生了什麼。 請記住: Nullable<something>
仍是速記的something?
是一種結構,編譯器彷佛爲此進行了大量工做,讓咱們將null看成類使用。
以下所示, SomeNullable == null
和SomeNullable.HasValue
將始終返回預期的true或false。 儘管下面沒有演示, SomeNullable == 3
也有效(假設SomeNullable是int?
)。
雖然SomeNullable.Value
會讓咱們運行時錯誤,若是咱們分配null
給SomeNullable
。 實際上,因爲重載運算符,重載object.Equals(obj)
方法以及編譯器優化和猴子業務的結合,這是惟一可能致使可爲空的問題引發咱們問題的狀況。 性能
這是我運行的一些代碼的描述,以及它在標籤中產生的輸出: 測試
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.")
好的,讓咱們嘗試下一個初始化方法: 優化
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.")
和之前同樣。 請記住,用int? val = new int?(null);
初始化int? val = new int?(null);
int? val = new int?(null);
,若是將null傳遞給構造函數,則會產生COMPILE時間錯誤,由於可爲空的對象的VALUE不可爲空。 只有包裝對象自己能夠等於null。 spa
一樣,咱們將從如下位置得到編譯時錯誤: code
int? val = new int?(); val.Value = null;
更不用說val.Value
仍是一個只讀屬性,這意味着咱們甚至不能使用如下內容: 對象
val.Value = 3;
可是一樣,多態重載的隱式轉換運算符使咱們可以:
val = 3;
只要它能正常工做,就不用擔憂雜亂的東西。 :)
在VB.Net中。 當可使用「 .HasValue」時,請勿使用「 IsNot Nothing」。 我剛剛解決了「操做可能破壞運行時的穩定性」的中等信任錯誤,方法是將「 IsNot Nothing」替換爲「 .HasValue」。 我真的不明白爲何,可是編譯器中發生的事情有所不一樣。 我認爲C#中的「!= null」可能有相同的問題。
通常答案和經驗法則:若是您有一個選擇(例如,編寫自定義序列化程序),能夠在與object
不一樣的管道中處理Nullable,並使用其特定的屬性,則能夠這樣作並使用Nullable的特定屬性。 所以,從一致的觀點來看,應首選HasValue
。 一致的想法能夠幫助您編寫更好的代碼,而沒必要花費太多時間在細節上。 例如,第二種方法會有效不少倍(主要是由於編譯器內聯和裝箱,可是數字仍然很是有表現力):
public static bool CheckObjectImpl(object o) { return o != null; } public static bool CheckNullableImpl<T>(T? o) where T: struct { return o.HasValue; }
基準測試:
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 |
基準代碼:
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
PS 。 人們說建議「因爲始終如一的想法而首選HasValue」是無關的和無用的。 您能夠預測其性能嗎?
public static bool CheckNullableGenericImpl<T>(T? t) where T: struct { return t != null; // or t.HasValue? }
PPS人們繼續CheckNullableGenericImpl
但沒有人試圖預測CheckNullableGenericImpl
性能。 並且那裏的編譯器不會幫助您用HasValue
替換!=null
。 若是您對性能感興趣,則應直接使用HasValue
。
若是您使用linq並但願使代碼簡短,我建議始終使用!=null
這就是爲何:
假設咱們有一些帶有可爲空的雙變量SomeDouble
Foo
類
public class Foo { public double? SomeDouble; //some other properties }
若是咱們想在代碼中的某個地方從Foo集合中獲取全部具備非null SomeDouble值的Foo(假設該集合中的某些foos也能夠爲null),那麼咱們將以致少三種方式編寫函數(若是咱們使用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 }
在這種狀況下,我建議老是選擇較短的