VB.NETとC#の値に対してnullをチェックすることに違いがあるのはなぜですか?


110

ではVB.NET、この処理が行われます。

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

しかし、C#ではこれが起こります:

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");
}

なぜ違いがあるのですか?


22
それは恐ろしいことです。
Mikeb 2013年

8
default(decimal?)ではなく0を返すと思いますnull
ライアンフレーム

7
@RyanFrame NO。それがあるので、NULL可能なタイプ、それが返されますnull
SonerGönül

4
そうそう...右... VBの中でIf条件文ブール値として評価する必要はありません ... uuuugh EDIT:だからがNothing <> Anything = Nothingでた結果If、負/他のルートを取って。
クリスシンクレア

13
@JMK:Null、Nothing、Emptyは実際にはすべて微妙に異なります。それらがすべて同じであれば、3つは必要ありません。
Eric Lippert

回答:


88

VB.NETとC#.NETは異なる言語であり、使用について異なる仮定をした異なるチームによって構築されました。この場合、NULL比較のセマンティクス。

私の個人的な好みはVB.NETセマンティクスであり、本質的には「まだ知らない」というセマンティクスをNULLにします。次に、5と「まだわかりません」を比較します。当然「私はまだ知りません」です。つまりNULL。これには、SQLデータベース(ほとんどではないにしても)のSQLデータベースでNULLの動作をミラーリングするという追加の利点があります。これは、ここで説明するように、3値ロジックのより標準的な(C#よりも)解釈です

C#チームは、NULLの意味についてさまざまな仮定を行ったため、表示される動作が異なりました。Eric LippertがC#でのNULLの意味についてブログを書いています。Eric Lippert氏:「VB / VBScriptとJScriptのnullのセマンティクスについてもここここで書きました」。

NULL値が発生する可能性のある環境では、除外された中間の法則(つまり、Aまたは〜Aがトートロジー的に真であること)はもはや信頼できないことを認識することが重要です。

更新:

A bool(とは対照的にbool?)のみTRUE値とFALSEをとることができます。ただし、NULLの言語実装では、NULLが式を通じてどのように伝播するかを決定する必要があります。VBでは、式5=null5<>null両方がfalseを返します。C#で、同等の式の5==null及び5!=null唯一第二最初- [PG 2014年3月2日更新]返し偽。ただし、nullをサポートするすべての環境では、プログラマーがその言語で使用される真理値表とnull伝播を知る必要があります。

更新

セマンティクスに関するEric Lippertのブログ記事(以下のコメントで言及)は次の場所にあります。


4
リンクをありがとう。VB / VBScriptとJScriptのnullのセマンティクスについてもここに書きました:blogs.msdn.com/b/ericlippert/archive/2003/09/30/53120.aspx とこちら:blogs.msdn.com/b/ericlippert/ archive / 2003/10/01 / 53128.aspx
Eric Lippert

27
そして、このようにC#をVBと非互換にするという決定は、物議を醸すものでした。当時、私は言語設計チームにいませんでしたが、この決定に至るまでの議論はかなりのものでした。
Eric Lippert

2
@ BlueRaja-DannyPflughoeft C#boolでは、3つの値を持つことはできず、2つだけです。それbool?は3つの値を持つことができるということです。operator ==また、operator !=どちらも、オペランドのタイプに関係boolなくbool?、を返します。さらに、ifステートメントはのみを受け入れ、boolは受け入れませんbool?
サービー2013年

1
C#では式5=null5<>nullは無効です。そして、5 == nullそして5 != null、それが戻ってくるのは2番目falseですか?
Ben Voigt 2014年

1
@BenVoigt:ありがとう。それらすべての賛成投票とあなたがそのタイプミスを発見した最初の人です。;-)
Pieter Geerkens 2014年

37

の代わりにx <> y戻るので。定義されていないため、単に定義されていません。(SQL nullと同様)。Nothingtruex

注:VB.NET Nothing<> C# null

また、値があるNullable(Of Decimal)場合にのみ、aの値を比較する必要があります。

したがって、上記のVB.NETはこれに似ています(見た目はそれほど正しくありません)。

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

VB.NET 言語仕様

7.1.1 Null許容値の型... Null許容値の型には、Null値だけでなく、その型のNull許容不可バージョンと同じ値を含めることができます。したがって、null可能値型の場合、型の変数にNothingを割り当てると、変数の値は値型のゼロ値ではなく、null値に設定されます。

例えば:

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) '

16
"VB.NET Nothing <> C#null" C#の場合はtrue、VB.Netの場合はfalseを返しますか?冗談です:-p
ken2k

17

生成されたCILを見てください(両方を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");
    }
}

Visual Basic:

[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");
    }
}

Visual Basicでの比較でNullable <bool>(bool、false、trueではない)が返されることがわかります。また、undefinedをboolに変換するとfalseになります。

NothingNothingVisual Basicではfalseではなく、常に何でも比較されます(SQLと同じです)。


なぜ試行錯誤で質問に答えるのですか?言語仕様からできるはずです。
David Heffernan

3
@DavidHeffernan、これはかなり明確な言語の違いを示しているためです。
nothrow 2013年

2
@ヨサリアンあなたはこの問題について言語仕様があいまいだと思います。同意しません。ILは、変更される可能性のある実装の詳細です。仕様はそうではありません。
サービー2013年

2
@DavidHeffernan:私はあなたの態度が好きで、試すことを勧めます。VB言語仕様は、解析が難しい場合があります。Lucianは数年前からこの問題を改善してきましたが、この種のコーナーケースの正確な意味を解明するのは依然として非常に困難です。仕様のコピーを入手し、いくつかの調査を行い、結果を報告することをお勧めします。
Eric Lippert

2
@Yossarian指定したILコードの実行結果は変更されることはありませんが、指定されたC#/ VBコードが表示されたILコードにコンパイルされること、変更される可能性があります(そのILの動作が言語仕様の定義とも一致します)。
2013年

6

ここで観察される問題は、より一般的な問題の特別なケースです。これは、少なくともいくつかの状況で役立つ可能性のある同等性のさまざまな定義の数が、それらを表現するために一般的に利用可能な手段の数を超えることです。この問題は、同等性をテストするさまざまな方法でさまざまな結果が得られると混乱するという不幸な信念によって悪化する場合があり、このような混乱は、さまざまな形式の同等性が可能な限り同じ結果をもたらすことで回避できる可能性があります。

実際には、混乱の根本的な原因は、異なるセマンティクスが異なる状況で有用であるという事実にもかかわらず、異なる形式の等式および不等式テストが同じ結果をもたらすと期待されるという誤った信念です。たとえば、算術の観点からは、Decimal末尾のゼロの数のみが異なるものを等しいと比較できるようにすると便利です。同様に、double正のゼロや負のゼロなどの値の場合。一方、キャッシングまたはインターンの観点からは、このようなセマンティクスは致命的となる可能性があります。たとえば、Dictionary<Decimal, String>あるmyDict[someDecimal]が等しいであると仮定しsomeDecimal.ToString()ます。このようなオブジェクトは、多くの場合Decimal文字列に変換する必要があり、多くの重複があると予想される値。残念ながら、このようなキャッシングを使用して12.3 mと12.40 mを変換し、その後12.30 mと12.4 mを変換すると、後者の値は「12.30」と「12.4」ではなく「12.3」と「12.40」になります。

手元の問題に戻ると、null許容オブジェクトが等しいかどうかを比較する賢明な方法が複数あります。C#は、その==オペレーターがの動作をミラーリングする必要があるという見方をしていEqualsます。VB.NETは、その動作を望む人なら誰でもEquals使用できるため、その動作は他のいくつかの言語の動作をミラーリングする必要があるという見方をしていますEquals。ある意味で、正しい解決策は3方向の「if」構成を持ち、条件式が3値の結果を返す場合、コードがnullケースで何が起こるかを指定する必要があることです。これは言語そのもののオプションではないため、次善の策は、異なる言語がどのように機能するかを学び、同じでないことを認識することです。

ちなみに、CにはないVisual Basicの「Is」演算子を使用して、null許容オブジェクトが実際にnullかどうかをテストできます。ifテストがを受け入れるかどうかを合理的に疑問に思うかもしれませんが、null許容型で呼び出されたときではなくBoolean?、通常の比較演算子を返すようにすると便利です。ちなみに、VB.NETでは、ではなく等号演算子を使用しようとすると、比較の結果が常にになるという警告が表示され、何かがnullかどうかをテストする場合に使用する必要があります。Boolean?BooleanIsNothingIs


C#でクラスがnullかどうかのテストは、によって行われ== nullます。また、null許容値型に値があるかどうかのテストは、によって行われ.hasValueます。Is Nothingオペレーターにはどんな用途がありますか?C#にはありますisが、型の互換性をテストします。これらに照らして、最後の段落が何を言おうとしているのかは本当にわかりません。
ErikE 2016

@ErikE:vb.netとC#の両方で、null許容型の値をとの比較を使用してチェックできますが、null両方の言語HasValueは、少なくとも型がわかっている場合(それがわからない場合)、チェックの構文糖として扱いますジェネリック用に生成されるコード)。
スーパーキャット2016

ジェネリックでは、null許容型とオーバーロードの解決に関するトリッキーな問題が発生する可能性があります...
ErikE

3

かもしれ この ポストも助けます:

私の記憶が正しければ、VBの「なし」は「デフォルト値」を意味します。値タイプの場合、これはデフォルト値です。参照タイプの場合、それはnullになります。したがって、構造体に何も割り当てなくてもまったく問題ありません。


3
これは質問の答えにはなりません。
David Heffernan

いいえ、それは何も明らかにしません。問題は<>、VB の演算子と、それがnull許容型でどのように機能するかについてです。
David Heffernan

2

これは、VBの明確な奇妙さです。

VBでは、2つのnull許容型を比較す​​る場合は、を使用する必要がありますNullable.Equals()

あなたの例では、それは:

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

5
慣れていないときは「奇妙さ」。Pieter Geerkensの回答をご覧ください。
rskar

まあ、VBがの動作を再現しないのも奇妙だと思いますNullable<>.Equals()。同じように機能することを期待するかもしれません(これはC#が行うことです)。
マシューワトソン

「期待するかもしれない」のように、期待は人が経験したものに関するものです。C#は、Javaユーザーの期待を念頭に置いて設計されました。Javaは、C / C ++ユーザーの期待を念頭に置いて設計されました。良くも悪くも、VB.NETはVB6ユーザーの期待を念頭に置いて設計されました。stackoverflow.com/questions/14837209/…stackoverflow.com/questions/10176737/…の
rskar

1
@MatthewWatsonの定義はNullable、.NETの最初のバージョンには存在しませんでした。C#とVB.NETがしばらくの間使用されておらず、ヌル伝播の動作がすでに決定されていた後に作成されました。正直に言って、この言語が数年前から作成されていない型と一致していると思いますか?VB.NETプログラマーの観点から見ると、Nullable.Equalsは言語と一貫性がなく、逆ではありません。(C#とVBの両方が同じNullable定義を使用していると仮定すると、両方の言語と一致する方法はありませんでした。)
Servy

0

VBコードは単に正しくありません。 "x <> y"を "x = y"に変更しても、結果として "false"が残ります。null許容インスタンスの最も一般的な表現方法は "Not x.Equals(y)"であり、C#の "x!= y"と同じ動作になります。


1
ない限りxnothing、その場合にはx.Equals(y)例外がスローされます。
2013年

@Servy:(数年後)これに再び遭遇し、私があなたを修正していないことに気づきました-"x.Equals(y)"はnull可能な型インスタンス 'x'の例外をスローしません。Null許容型は、コンパイラによって異なる方法で処理されます。
Dave Doknjas

具体的には、「null」に初期化されたnullableインスタンスは、実際にはnullに設定された変数ではなく、値が設定されていないSystem.Nullableインスタンスです。
Dave Doknjas
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.