どちらが望ましいですか:Nullable <T> .HasValueまたはNullable <T>!= null?


437

Nullable<>.HasValueはセマンティクスが好きだったので、いつも使っていました。しかし、最近私は他の誰かの既存のコードベースに取り組んでいて、Nullable<> != null代わりに排他的に使用していました。

どちらか一方を使用する理由はありますか、それとも純粋に好みですか?

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

  1. int? b;
    if (b != null)
        // ...

9
私は...同様の質問をいくつかの良い答えを得た:stackoverflow.com/questions/633286/...
nailitdown

3
個人的にはHasValue記号よりも言葉の方が読みやすいと思うので使います。それはすべてあなた次第であり、何があなたの既存のスタイルに適合します。
Jake Petroules、

1
.HasValue型がT?文字列などのnullにできる型ではなく型であることを示すため、より意味があります。
user3791372

回答:


476

コンパイラーはnull比較をへの呼び出しに置き換えるHasValueため、実際の違いはありません。あなたとあなたの同僚にとってより読みやすい/より意味のあるものを実行してください。


86
さらに、「どちらかがより一貫性があるか、既存のコーディングスタイルに従っているか」を追加します。
ジョシュ・リー

20
ワオ。この構文糖分が嫌いです。int? x = nullnull可能インスタンスが参照型であるような錯覚を与えます。しかし、真実はNullable <T>が値型であることです。NullReferenceExceptionを実行すると感じます:int? x = null; Use(x.HasValue)
KFL 2014年

11
@KFL構文上の砂糖が気になる場合は、のNullable<int>代わりに使用してくださいint?
Cole Johnson、

24
アプリケーションの作成の初期段階では、一部のデータを格納するためにnull可能な値の型を使用するだけで十分だと思うかもしれませんが、しばらくして、目的に適したクラスが必要であることを理解します。nullと比較する元のコードを作成すると、HasValue()へのすべての呼び出しをnull比較で検索/置換する必要がないという利点があります。
アンデルス

14
Nullable と呼ばれていることを考えると、Nullableをnullに設定したり、nullと比較したりできることについて不平を言うのはかなりばかげています。問題は、人々が「参照型」を「nullにすることができる」と混同していることですが、それは概念的な混乱です。将来のC#には、null不可の参照型があります。
ジムバルター2016年

48

私が好む(a != null)構文マッチがタイプを参照するようにします。


11
もちろん、これNullable<>は参照型ではないので、かなり誤解を招きます。
Luaan

9
はい。しかし、nullチェックを行っている時点では、事実はほとんど問題になりません。
cbp 2015

31
概念的に混乱しているだけです。2つの異なる型に一貫した構文を使用しても、それらが同じ型であるとは限りません。C#にはnull可能な参照型(すべての参照型は現在null可能ですが、将来的に変更される予定)とnull可能な値型があります。すべてのnull許容型に一貫した構文を使用することには意味があります。null許容値型が参照型であること、またはnull許容参照型が値型であることを意味するものではありません。
ジムバルター、2015年

HasValueそれより読みやすいので、私は好む!= null
Konrad

同じコードを記述する異なるスタイルを混在させない場合、コーディングの一貫性はより読みやすくなります。すべての場所に.HasValueプロパティがあるわけではないため、一貫性を高めるために!= nullを使用するため、私の意見では。
ColacX

21

さまざまな方法を使用して値をnull許容のintに割り当てることで、これについていくつかの調査を行いました。いろいろなことをした時の様子です。何が起こっているのかを明確にする必要があります。覚えておいてください:Nullable<something>または、略記something?は、それがクラスであるかのようにnullで使用できるようにするためにコンパイラーが多くの作業を行っているように見える構造体です。
あなたは以下を参照してくださいよ、通りSomeNullable == nullSomeNullable.HasValue常に期待trueまたはfalseを返します。以下でSomeNullable == 3は説明しませんが、も有効です(SomeNullableがであると想定int?)。
一方でSomeNullable.Value、我々が割り当てられている場合、私たちにランタイムエラーを取得nullする方法、およびコンパイラの最適化とモンキービジネス。SomeNullableます。これは、オーバーロードされた演算子とオーバーロードされた演算子の組み合わせのおかげで、nullableが問題を引き起こす可能性がある唯一のケースです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);nullをコンストラクターに渡してで初期化すると、NULL可能オブジェクトのVALUEはNULL可能ではないため、コンパイル時エラーが発生することに注意してください。nullに等しくなるのは、ラッパーオブジェクト自体だけです。

同様に、次の場合にコンパイル時エラーが発生します。

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

val.Valueとにかく、それが読み取り専用のプロパティであることは言うまでもありません。つまり、次のようなものは使用できません。

val.Value = 3;

しかし、再び、多形のオーバーロードされた暗黙の変換演算子を使用して、

val = 3;

それが正しく機能する限り、何の問題でもpolysomthing whatchamacallitsを心配する必要はありませんか?:)


5
「覚えておいてください:Nullable <something>または省略形の何か?はクラスです。」これは間違っています!Nullable <T>は構造体です。Equalsおよび==演算子をオーバーロードして、nullと比較したときにtrueを返します。コンパイラーは、この比較のために特別な作業を行いません。
andrewjs

1
@andrewjs-構造体(クラスではない)であることは正しいですが、==演算子をオーバーロードするのは誤りです。あなたが入力した場合Nullable<X>のVisualStudio 2013とF12それで、あなたはそれが唯一にしてから変換オーバーロードしていることがわかりますXし、Equals(object other)方法を。ただし、==演算子はデフォルトでこのメソッドを使用するので、効果は同じです。私は実際、しばらくの間、この事実についてこの回答を更新するつもりでしたが、私は怠惰で忙しいです。このコメントは今のところ必要があります:)
Perrin Larson '11年

私はildasmを介して簡単なチェックを行いましたが、あなたはコンパイラーが魔法をかけていることについて正しいです。Nullable <T>オブジェクトをnullと比較すると、実際にはHasValueの呼び出しに変換されます。面白い!
andrewjs

3
@andrewjs実際、コンパイラはnullableを最適化するために大量の作業を行います。たとえば、null許容型に値を割り当てても、実際にはnull可能ではありません(例:)int? val = 42; val.GetType() == typeof(int)。したがって、null可能である構造体は、nullと等しくなる可能性があるだけでなく、しばしばnull可能でもありません!:D同じように、null許容値をボックス化すると、ボクシングintではなくint?- ボクシングになります。またint?、に値がない場合nullは、ボックス化されたnull可能値の代わりに取得されます。それは基本的にnullableを適切に使用することによるオーバーヘッドがほとんどないことを意味します:)
Luaan

1
@JimBalter本当に?それはとても興味深いです。では、メモリプロファイラーはクラスのnull許容フィールドについて何を伝えているのでしょうか。C#で別の値の型から継承する値の型をどのように宣言しますか?.NETのnull許容型と同じように動作する独自のnull許容型をどのように宣言しますか?あるとき以来Null.NETのタイプ?CLR / C#仕様の中でそれが言われている部分を指摘できますか?NullableはCLR仕様で明確に定義されており、その動作は「抽象化の実装」ではなく、コントラクトです。しかし、あなたができる最善がアドホミネム攻撃であるならば、楽しんでください。
Luaan、2016年

13

VB.Net。「.HasValue」を使用できる場合は、「IsNot Nothing」を使用しないでください。「IsNot Nothing」を「.HasValue」に置き換えることで、「オペレーションがランタイムを不安定にする可能性がある」中程度の信頼エラーを1つの場所で解決しました。理由はよくわかりませんが、コンパイラで何か違うことが起こっています。C#の "!= null"でも同じ問題が発生する可能性があると思います。


8
HasValue読みやすさのために私は好みます。IsNot Nothing本当に醜い表現です(二重否定のため)。
Stefan Steinegger 2013

12
@steffan "IsNot Nothing"は二重否定ではありません。「何も」は否定的なものではなく、プログラミングの領域外であっても、個別の量です。「この量は何もないわけではない。」文法的には、「この量はゼロではない」とまったく同じです。どちらもダブルネガティブではありません。
jmbpiano

5
ここで真実の欠如に反対したくないというわけではありませんが、今から始めましょう。IsNot何も明らかに、よく、過度に否定的です。HasValueのような明確で明確なものを書いてみませんか?これは文法テストではなく、コーディングであり、主な目的は明快さです。
Randy Gamage 2015

3
jmbpiano:私はそれが二重否定ではないことに同意しますが、それは単一否定であり、単純な肯定的な表現ほど醜く、それほど明確ではありません。
Kaveh Hadjari 16年

0

linqを使用していてコードを短くしたい場合は、常に使用することをお勧めします !=null

そしてこれが理由です:

null許容のdouble変数を持つクラスがあるFooとします。SomeDouble

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

コード内のどこかで、FooのコレクションからすべてのFooをnull以外のSomeDouble値で取得したい場合(コレクション内の一部のfooもnullである可能性があると想定)、関数を作成する少なくとも3つの方法になります( 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
}

このような状況では、常に短い方に行くことをお勧めします


2
はい、foo?.SomeDouble.HasValueそのコンテキストでのコンパイル時エラー(私の用語では「スロー」ではbool?ありません)は、型がだけではなくであるためですbool。(.Whereメソッドはaを必要としFunc<Foo, bool>ます。)(foo?.SomeDouble).HasValueもちろん、それはtypeを持っているので、それを行うことができboolます。これは、C#コンパイラによって(少なくとも正式には)最初の行が内部的に「変換」されたものです。
Jeppe Stig Nielsen

-6

一般的な答えと経験則:別のパイプラインでNullableを処理するオプション(たとえば、カスタムシリアライザーを作成する)がある object場合、その固有のプロパティを使用して、Nullable固有のプロパティを使用します。したがって、一貫した思考の観点からHasValueは、優先する必要があります。一貫した考え方は、詳細に多くの時間を費やさずに、より良いコードを書くのに役立ちます。たとえば、2番目の方法は何倍も効果的です(主にコンパイラーのインライン化とボクシングが原因ですが、それでも数値は非常に表現力があります)。

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。そして、コンパイラはあなたがで置き換えるの!=nullを助けませんHasValueHasValueパフォーマンスに関心がある場合は、直接使用する必要があります。


2
あなたのCheckObjectImpl にNULL可能object、一方でCheckNullableImplボクシングを使用していません。したがって、比較は非常に不公平です。だけでなく、それはで述べたように、それは、また、役に立たない理由で、運賃はなく、受け入れ答え、コンパイラの書き換え!=HasValueとにかく。
GSerg

2
読者はNullable<T>、構造体の性質を無視しないでください(それをにボックス化することによってobject)。!= null左側にnullable を適用すると!=、コンパイラーレベルでnullable のサポートが機能するため、ボクシングは発生しません。nullableを最初ににボックス化してコンパイラーから非表示にする場合は異なりますobject。どちらのCheckObjectImpl(object o)ベンチマークも原則として意味がありません。
GSerg

3
私の問題は、このウェブサイトのコンテンツの品質を気にすることです。あなたが投稿したものは、誤解を招くか間違っています。あなたはOPの質問に答えるしようとしていた場合は、あなたの答えは、交換することによって証明することは容易であるフラットアウト間違っているコールをするCheckObjectImplのにボディの内部CheckObject。ただし、最新のコメントでは、この8年前の質問に回答することを決めたときに、実際にはまったく異なる質問があったことを明らかにしているため、元の質問の文脈で誤解を招く可能性があります。それはOPが求めていたものではありませんでした。
GSerg

3
ググる次の男の立場に身を置くwhat is faster != or HasValue。彼はこの質問に到達し、あなたの答えを閲覧し、あなたのベンチマークを高く評価し、「ああ、それは!=明らかに非常に遅いので使用しません!」と言います。これは非常に間違った結論であり、彼はそれを広めていきます。それが私があなたの答えが有害であると私が思う理由です-それは間違った質問に答え、したがって疑いを持たない読者に間違った結論を植え付けます。あなたを変更したときに何が起こるか考えてみましょうCheckNullableImplすること可能return o != null;あなたは同じベンチマーク結果を取得します。
GSerg

8
私はあなたの答えについて議論しています。それが違いを示すようにあなたの答えは一見に見える!=し、HasValue実際にそれが違いを示すobject oとしますT? o。私が提案したことを実行する場合、つまりCheckNullableImplとして書き換えるpublic static bool CheckNullableImpl<T>(T? o) where T: struct { return o != null; }と、ベンチマークが!=よりもはるかに遅いことを明確に示すことになります!=。あなたの答えが説明する問題は、!=vs についてではないという結論にあなたを導くはずですHasValue
GSerg
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.