Warum gibt es einen Unterschied beim Prüfen von Null gegen einen Wert in VB.NET und C #?

110

In VB.NET passiert Folgendes :

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

Aber in C # passiert Folgendes:

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

Warum gibt es einen Unterschied?

blindmeis
quelle
22
das ist erschreckend.
Mikeb
8
Ich glaube, es default(decimal?)wird 0 zurückgegeben, nicht null.
Ryan Frame
7
@ RyanFrame NO. Da es sich um nullbare Typen handelt , wird es zurückgegebennull
Soner Gönül
4
Oh ja ... richtig ... in VB- IfBedingungen muss nicht als Boolescher Wert ausgewertet werden ... uuuugh EDIT: Das Nothing <> Anything = Nothingführt also dazu, Ifdass der negative / else-Weg eingeschlagen wird.
Chris Sinclair
13
@JMK: Null, Nothing und Empty sind eigentlich alle subtil unterschiedlich. Wenn sie alle gleich wären, würden Sie nicht drei von ihnen brauchen.
Eric Lippert

Antworten:

88

VB.NET und C # .NET sind verschiedene Sprachen, die von verschiedenen Teams erstellt wurden, die unterschiedliche Annahmen zur Verwendung getroffen haben. in diesem Fall die Semantik eines NULL-Vergleichs.

Meine persönliche Präferenz ist die VB.NET-Semantik, die NULL im Wesentlichen die Semantik "Ich weiß es noch nicht" gibt. Dann der Vergleich von 5 mit "Ich weiß es noch nicht". ist natürlich "Ich weiß es noch nicht"; dh NULL. Dies hat den zusätzlichen Vorteil, dass das Verhalten von NULL in (den meisten, wenn nicht allen) SQL-Datenbanken gespiegelt wird. Dies ist auch eine Standardinterpretation (als die von C #) der dreiwertigen Logik, wie hier erläutert .

Das C # -Team hat unterschiedliche Annahmen darüber getroffen, was NULL bedeutet, was zu dem von Ihnen gezeigten Verhaltensunterschied führt. Eric Lippert schrieb einen Blog über die Bedeutung von NULL in C # . Per Eric Lippert: "Ich habe hier und hier auch über die Semantik von Nullen in VB / VBScript und JScript geschrieben. "

In jeder Umgebung, in der NULL-Werte möglich sind, ist es wichtig zu erkennen, dass das Gesetz der ausgeschlossenen Mitte (dh dass A oder ~ A tautologisch wahr ist) nicht mehr verwendet werden kann.

Aktualisieren:

A bool(im Gegensatz zu a bool?) kann nur die Werte TRUE und FALSE annehmen. Eine Sprachimplementierung von NULL muss jedoch entscheiden, wie sich NULL durch Ausdrücke ausbreitet. In VB geben die Ausdrücke 5=nullund 5<>nullBEIDE false zurück. In C # wird von den vergleichbaren Ausdrücken 5==nullund 5!=nullnur dem zweiten ersten [aktualisiert am 02.03.2014 - PG] false zurückgegeben. In JEDER Umgebung, die null unterstützt, muss der Programmierer jedoch die von dieser Sprache verwendeten Wahrheitstabellen und die Null-Weitergabe kennen.

Aktualisieren

Eric Lipperts Blog-Artikel (in seinen Kommentaren unten erwähnt) zur Semantik finden Sie jetzt unter:

Pieter Geerkens
quelle
4
Danke für den Link. Ich habe auch über die Semantik von Nullen in VB / VBScript und JScript hier geschrieben: blogs.msdn.com/b/ericlippert/archive/2003/09/30/53120.aspx und hier: blogs.msdn.com/b/ericlippert/ Archiv / 2003/10/01 / 53128.aspx
Eric Lippert
27
Zu Ihrer Information war die Entscheidung, C # auf diese Weise mit VB inkompatibel zu machen, umstritten. Ich war zu dieser Zeit nicht im Sprachdesign-Team, aber der Umfang der Debatten, die in diese Entscheidung einflossen, war beträchtlich.
Eric Lippert
2
@ BlueRaja-DannyPflughoeft In C # boolkann nicht 3 Werte haben, nur zwei. Das bool?kann drei Werte haben. operator ==und operator !=beide geben boolnicht zurück bool?, unabhängig vom Typ der Operanden. Darüber hinaus kann eine ifAnweisung nur a akzeptieren bool, nicht a bool?.
Servy
1
In C # sind die Ausdrücke 5=nullund 5<>nullungültig. Und von 5 == nullund 5 != null, sind Sie sicher, dass es die Sekunde ist, die zurückkehrt false?
Ben Voigt
1
@ BenVoigt: Danke. All diese Up-Votes und Sie sind der erste, der diesen Tippfehler entdeckt. ;-)
Pieter Geerkens
37

Weil x <> yzurück Nothingstatt true. Es ist einfach nicht definiert, da xes nicht definiert ist. (ähnlich wie SQL null).

Hinweis: VB.NET Nothing<> C # null.

Sie müssen den Wert von a auch Nullable(Of Decimal)nur vergleichen, wenn er einen Wert hat.

Das obige VB.NET vergleicht also ähnlich (was weniger falsch aussieht):

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

Die VB.NET- Sprachspezifikation :

7.1.1 Nullwertbare Werttypen ... Ein Nullwertwerttyp kann dieselben Werte wie die nicht nullwertfähige Version des Typs sowie den Nullwert enthalten. Wenn Sie also für einen nullbaren Werttyp einer Variablen des Typs Nothing zuweisen, wird der Wert der Variablen auf den Nullwert und nicht auf den Nullwert des Werttyps gesetzt.

Beispielsweise:

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) '
Tim Schmelter
quelle
16
"VB.NET Nothing <> C # null" gibt es für C # true und für VB.Net false zurück? Nur ein Scherz :-p
Ken2k
17

Schauen Sie sich die generierte CIL an (ich habe beide in C # konvertiert):

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

Sie werden sehen, dass der Vergleich in Visual Basic Nullable <bool> zurückgibt (nicht bool, false oder true!). Und undefiniert in bool konvertiert ist falsch.

Nothingim Vergleich zu allem, was Nothingin Visual Basic immer falsch ist (es ist dasselbe wie in SQL).

nothrow
quelle
Warum die Frage durch Ausprobieren beantworten? Sollte möglich sein, dies anhand der Sprachspezifikationen zu tun.
David Heffernan
3
@ DavidHeffernan, weil dies den Unterschied in der Sprache zeigt, der ziemlich eindeutig ist.
Nothrow
2
@ Yossarian Sie denken, dass die Sprachspezifikationen zu diesem Thema nicht eindeutig sind. Ich bin nicht einverstanden. Die IL ist ein Implementierungsdetail, das Änderungen unterliegen kann. Die Spezifikationen sind nicht.
Servy
2
@ DavidHeffernan: Ich mag deine Einstellung und ermutige dich, es zu versuchen. Die VB-Sprachspezifikation kann manchmal schwierig zu analysieren sein. Lucian hat es seit einigen Jahren verbessert, aber es kann immer noch schwierig sein, die genauen Bedeutungen dieser Art von Eckfällen herauszufinden. Ich schlage vor, dass Sie eine Kopie der Spezifikation erhalten, Nachforschungen anstellen und Ihre Ergebnisse melden.
Eric Lippert
2
@Yossarian Die Ergebnisse des IL - Code ausgeführt werden Sie zur Verfügung gestellt haben , ist nicht Änderungen vorbehalten, sondern dass der C # / VB - Code zur Verfügung gestellt wird in den IL - Code kompiliert werden Sie gezeigt , ist Änderungen vorbehalten (solange das Verhalten , dass IL ist auch in Übereinstimmung mit der Definition der Sprachspezifikationen).
Servy
6

Das Problem, das hier beobachtet wird, ist ein Sonderfall eines allgemeineren Problems, nämlich dass die Anzahl der verschiedenen Definitionen der Gleichheit, die zumindest unter bestimmten Umständen nützlich sein können, die Anzahl der allgemein verfügbaren Mittel übersteigt, um sie auszudrücken. Dieses Problem wird in einigen Fällen durch die unglückliche Annahme verschlimmert, dass es verwirrend ist, unterschiedliche Mittel zum Testen der Gleichheit zu verwenden, um unterschiedliche Ergebnisse zu erzielen, und eine solche Verwirrung könnte vermieden werden, indem die verschiedenen Formen der Gleichheit nach Möglichkeit dieselben Ergebnisse liefern.

In der Realität ist die fundamentale Ursache für Verwirrung eine fehlgeleitete Annahme, dass die verschiedenen Formen der Gleichheits- und Ungleichheitsprüfung das gleiche Ergebnis liefern sollten, ungeachtet der Tatsache, dass unterschiedliche Semantiken unter verschiedenen Umständen nützlich sind. Aus arithmetischer Sicht ist es beispielsweise nützlich, solche zu haben, Decimaldie sich nur in der Anzahl der nachfolgenden Nullen unterscheiden, die als gleich verglichen werden. Ebenso für doubleWerte wie positive Null und negative Null. Andererseits kann eine solche Semantik vom Caching- oder Internierungsstandpunkt aus tödlich sein. Nehmen wir zum Beispiel an, man hätte eine Dictionary<Decimal, String>solche, myDict[someDecimal]die gleich sein sollte someDecimal.ToString(). Ein solches Objekt würde vernünftig erscheinen, wenn man viele hätteDecimalWerte, die man in einen String konvertieren wollte und erwartete, dass es viele Duplikate geben würde. Wenn ein solches Caching zum Konvertieren von 12,3 m und 12,40 m gefolgt von 12,30 m und 12,4 m verwendet würde, würden die letzteren Werte leider "12,3" und "12,40" anstelle von "12,30" und "12,4" ergeben.

Zurück zur Sache: Es gibt mehr als eine sinnvolle Möglichkeit, nullbare Objekte auf Gleichheit zu vergleichen. C # vertritt den Standpunkt, dass sein ==Operator das Verhalten von widerspiegeln sollte Equals. VB.NET vertritt den Standpunkt, dass sein Verhalten das einiger anderer Sprachen widerspiegeln sollte, da jeder, der das EqualsVerhalten wünscht, es verwenden könnte Equals. In gewissem Sinne wäre die richtige Lösung, ein Drei-Wege-"Wenn" -Konstrukt zu haben und zu verlangen, dass der Code angeben muss, was in dem nullFall geschehen soll, wenn der bedingte Ausdruck ein dreiwertiges Ergebnis zurückgibt . Da dies bei Sprachen, wie sie sind, keine Option ist, besteht die nächstbeste Alternative darin, einfach zu lernen, wie verschiedene Sprachen funktionieren, und zu erkennen, dass sie nicht gleich sind.

Im Übrigen kann der in C fehlende Operator "Is" von Visual Basic verwendet werden, um zu testen, ob ein nullbares Objekt tatsächlich null ist. Während man sich vernünftigerweise fragen könnte, ob ein ifTest a akzeptieren sollte, ist es eine nützliche Funktion Boolean?, wenn die normalen Vergleichsoperatoren zurückkehren, Boolean?anstatt Booleanwenn sie für nullfähige Typen aufgerufen werden. Übrigens, wenn man in VB.NET versucht, den Gleichheitsoperator anstelle von zu verwenden Is, erhält man eine Warnung, dass das Ergebnis des Vergleichs immer sein wird Nothing, und man sollte verwenden, Iswenn man testen möchte, ob etwas null ist.

Superkatze
quelle
Das Testen, ob eine Klasse in C # null ist, wird von durchgeführt == null. Das Testen, ob ein nullbarer Werttyp einen Wert hat, erfolgt durch .hasValue. Welchen Nutzen hat ein Is NothingBediener? C # hat zwar is, testet aber die Typkompatibilität. In Anbetracht dessen bin ich mir wirklich nicht sicher, was Ihr letzter Absatz zu sagen versucht.
ErikE
@ErikE: Sowohl vb.net als auch C # ermöglichen die Überprüfung von nullbaren Typen auf einen Wert anhand eines Vergleichs mit null, obwohl beide Sprachen dies als syntaktischen Zucker für eine HasValueÜberprüfung behandeln, zumindest in Fällen, in denen der Typ bekannt ist (ich bin nicht sicher) Welcher Code wird für Generika generiert?
Supercat
In Generika können Sie knifflige Probleme in
Bezug auf nullbare
3

Kann sein , diesem Post gut helfen Ihnen:

Wenn ich mich richtig erinnere, bedeutet "Nichts" in VB "den Standardwert". Für einen Werttyp ist dies der Standardwert für einen Referenztyp, der null wäre. Daher ist es überhaupt kein Problem, einer Struktur nichts zuzuweisen.

Evgenyl
quelle
3
Dies beantwortet die Frage nicht.
David Heffernan
Nein, es klärt nichts. Die Frage <>dreht sich alles um den Operator in VB und wie er mit nullbaren Typen arbeitet.
David Heffernan
2

Dies ist definitiv eine Verrücktheit von VB.

Wenn Sie in VB zwei nullfähige Typen vergleichen möchten, sollten Sie verwenden Nullable.Equals().

In Ihrem Beispiel sollte es sein:

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
Matthew Watson
quelle
5
Es ist "Verrücktheit", wenn es nicht vertraut ist. Siehe Antwort von Pieter Geerkens.
Rskar
Nun, ich finde es auch seltsam, dass VB das Verhalten von nicht reproduziert Nullable<>.Equals(). Man könnte erwarten, dass es genauso funktioniert (was C # tut).
Matthew Watson
Erwartungen, wie in "was man erwarten könnte", beziehen sich auf das, was man erlebt hat. C # wurde unter Berücksichtigung der Erwartungen von Java-Benutzern entwickelt. Java wurde unter Berücksichtigung der Erwartungen von C / C ++ - Benutzern entwickelt. Ob gut oder schlecht, VB.NET wurde mit Blick auf die Erwartungen der VB6-Benutzer entwickelt. Weitere Denkanstöße unter stackoverflow.com/questions/14837209/… und stackoverflow.com/questions/10176737/…
rskar
1
@MatthewWatson Die Definition von Nullablewar in den ersten Versionen von .NET nicht vorhanden. Sie wurde erstellt, nachdem C # und VB.NET einige Zeit nicht verfügbar waren und bereits ihr Null-Ausbreitungsverhalten bestimmt hatten. Erwarten Sie ehrlich, dass die Sprache mit einem Typ übereinstimmt, der seit mehreren Jahren nicht mehr erstellt wurde? Aus der Sicht eines VB.NET-Programmierers ist es Nullable.Equals, das nicht mit der Sprache übereinstimmt, sondern umgekehrt. (Da C # und VB beide dieselbe NullableDefinition verwenden, gab es keine Möglichkeit, dass sie mit beiden Sprachen übereinstimmen.)
Servy
0

Ihr VB-Code ist einfach falsch - wenn Sie "x <> y" in "x = y" ändern, wird immer noch "false" als Ergebnis angezeigt. Die gebräuchlichste Ausdrucksweise für nullfähige Instanzen ist "Not x.Equals (y)". Dies führt zu demselben Verhalten wie "x! = Y" in C #.

Dave Doknjas
quelle
1
Es xsei denn nothing, in diesem Fall x.Equals(y)wird eine Ausnahme ausgelöst.
Servy
@Servy: Stolperte erneut darüber (viele Jahre später) und bemerkte, dass ich Sie nicht korrigiert habe - "x.Equals (y)" löst keine Ausnahme für die nullbare Typinstanz 'x' aus. Nullable-Typen werden vom Compiler unterschiedlich behandelt.
Dave Doknjas
Insbesondere ist eine auf 'null' initialisierte nullfähige Instanz nicht wirklich eine auf null gesetzte Variable, sondern eine System.Nullable-Instanz ohne festgelegten Wert.
Dave Doknjas