web-dev-qa-db-ja.com

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

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

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

109
blindmeis

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 and JScript here and here "で記述しました。

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

更新:

boolは(bool?とは対照的に)、TRUEおよびFALSEの値のみを取ることができます。ただし、NULLの言語実装では、NULLが式を通じてどのように伝播するかを決定する必要があります。 VB式5=nullおよび5<>null BOTHはfalseを返します。C#では、比較可能な式5==nullおよび5!=nullのみ 二番目 最初[2014-03-02更新-PG]はfalseを返します。ただし、nullをサポートするすべての環境では、その言語で使用される真理値表とnull伝播を知ることはプログラマーの責任です。

87
Pieter Geerkens

_x <> y_はNothingではなくtrueを返すためです。 xが定義されていないため、単に定義されていません。 (SQL nullと同様)。

注:VB.NET Nothing <> C#null

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

したがって、上記の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許容値タイプの場合、タイプの変数に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) '
_
37
Tim Schmelter

生成された [〜#〜] 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になります。

Nothingは常にNothingと比較され、Visual Basicではfalseではありません(SQLと同じです)。

17
nothrow

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

実際には、混乱の根本的な原因は、異なるセマンティクスが異なる状況で有用であるという事実にもかかわらず、異なる形式の等式および不等式テストが同じ結果をもたらすと期待されるという誤った信念です。たとえば、算術の観点からは、末尾のゼロの数だけが等しいと比較されるDecimalを持つことができると便利です。同様に、正のゼロや負のゼロなどのdouble値についても同様です。一方、キャッシングまたはインターンの観点からは、このようなセマンティクスは致命的となる可能性があります。たとえば、_Dictionary<Decimal, String>_がsomeDecimal.ToString()と等しくなるように_myDict[someDecimal]_があったとします。このようなオブジェクトは、文字列に変換したい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テストが_Boolean?_を受け入れるかどうかを合理的に疑問視するかもしれませんが、null許容型で呼び出されたときに通常の比較演算子がBooleanではなく_Boolean?_を返すようにすることは便利な機能です。ちなみに、VB.NETでは、Isではなく等値演算子を使用しようとすると、比較の結果が常にNothingになるという警告が表示され、何かがnullかどうかをテストする場合はIsを使用する必要があります。

6
supercat

多分 this 投稿はあなたを助けます:

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

3
evgenyl

これは、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
2
Matthew Watson

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

0
Dave Doknjas