Pourquoi y a-t-il une différence dans la vérification de null par rapport à une valeur dans VB.NET et C #?

110

Dans VB.NET, cela se produit:

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

Mais en C #, cela se produit:

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

Pourquoi y a-t-il une différence?

aveugle
la source
22
c'est terrifiant.
Mikeb
8
Je crois que default(decimal?)renvoie 0, non null.
Ryan Frame
7
@RyanFrame NO. Puisqu'il s'agit de types Nullable , il renvoienull
Soner Gönül
4
Oh ouais ... bon ... en VB, les Ifconditions ne nécessitent pas d'être évaluées comme un booléen ... uuuugh EDIT: Donc, Nothing <> Anything = Nothingce qui entraîne la Ifprise de la route négative / autre.
Chris Sinclair
13
@JMK: Null, Nothing et Empty sont en fait tous subtilement différents. S'ils étaient tous identiques, vous n'en auriez pas besoin de trois.
Eric Lippert

Réponses:

88

VB.NET et C # .NET sont des langages différents, construits par différentes équipes qui ont fait des hypothèses différentes sur l'utilisation; dans ce cas, la sémantique d'une comparaison NULL.

Ma préférence personnelle est pour la sémantique VB.NET, qui donne essentiellement à NULL la sémantique «je ne sais pas encore». Puis la comparaison de 5 à "Je ne sais pas encore". est naturellement "je ne sais pas encore"; c'est-à-dire NULL. Cela présente l'avantage supplémentaire de refléter le comportement de NULL dans (la plupart sinon la totalité) des bases de données SQL. C'est aussi une interprétation plus standard (que celle de C #) de la logique à trois valeurs, comme expliqué ici .

L'équipe C # a fait différentes hypothèses sur la signification de NULL, ce qui a entraîné la différence de comportement que vous montrez. Eric Lippert a écrit un blog sur la signification de NULL en C # . Per Eric Lippert: "J'ai aussi écrit sur la sémantique des nulls en VB / VBScript et JScript ici et ici ".

Dans tout environnement dans lequel des valeurs NULL sont possibles, il est important de reconnaître que la loi du milieu exclu (c'est-à-dire que A ou ~ A est tautologiquement vrai) ne peut plus être invoquée.

Mettre à jour:

A bool(par opposition à a bool?) ne peut prendre que les valeurs TRUE et FALSE. Cependant, une implémentation de langage de NULL doit décider de la façon dont NULL se propage à travers les expressions. Dans VB, les expressions 5=nullet 5<>nullLES DEUX renvoient false. En C #, des expressions comparables 5==nullet 5!=nullseule la deuxième première [mise à jour 2014-03-02 - PG] renvoie false. Cependant, dans N'IMPORTE QUEL environnement qui prend en charge NULL, il incombe au programmeur de connaître les tables de vérité et la propagation Null utilisées par ce langage.

Mettre à jour

Les articles du blog d'Eric Lippert (mentionnés dans ses commentaires ci-dessous) sur la sémantique sont maintenant à:

Pieter Geerkens
la source
4
Merci pour le lien. J'ai également écrit sur la sémantique des nulls dans VB / VBScript et JScript ici: blogs.msdn.com/b/ericlippert/archive/2003/09/30/53120.aspx et ici: blogs.msdn.com/b/ericlippert/ archive / 2003/10/01 / 53128.aspx
Eric Lippert
27
Et pour info, la décision de rendre C # incompatible avec VB de cette manière était controversée. Je ne faisais pas partie de l'équipe de conception du langage à l'époque, mais la quantité de débats qui a abouti à cette décision a été considérable.
Eric Lippert
2
@ BlueRaja-DannyPflughoeft En C # boolne peut pas avoir 3 valeurs, seulement deux. C'est bool?ça qui peut avoir trois valeurs. operator ==et les operator !=deux renvoient bool, non bool?, quel que soit le type des opérandes. De plus, une ifinstruction ne peut accepter qu'un bool, pas un bool?.
Servy
1
En C #, les expressions 5=nullet 5<>nullne sont pas valides. Et de 5 == nullet 5 != null, êtes-vous sûr que c'est le second qui revient false?
Ben Voigt
1
@BenVoigt: Merci. Tous ces votes positifs et vous êtes le premier à repérer cette faute de frappe. ;-)
Pieter Geerkens
37

Parce que x <> yrevient Nothingau lieu de true. Il n'est tout simplement pas défini car il xn'est pas défini. (similaire à SQL null).

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

Vous devez également comparer la valeur de a Nullable(Of Decimal)uniquement si elle a une valeur.

Ainsi, le VB.NET ci-dessus se compare à ceci (qui semble moins incorrect):

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

La spécification du langage VB.NET :

7.1.1 Types de valeur Nullable ... Un type de valeur Nullable peut contenir les mêmes valeurs que la version non Nullable du type ainsi que la valeur Null. Ainsi, pour un type valeur Nullable, l'affectation de Nothing à une variable du type définit la valeur de la variable sur la valeur NULL, et non sur la valeur zéro du type valeur.

Par exemple:

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
la source
16
"VB.NET Nothing <> C # null" renvoie-t-il vrai pour C # et faux pour VB.Net? Je plaisante
:-p
17

Regardez le CIL généré (j'ai converti les deux en 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");
    }
}

Vous verrez que la comparaison dans Visual Basic renvoie Nullable <bool> (pas booléen, false ou true!). Et undefined converti en booléen est faux.

Nothingpar rapport à ce qui est toujours Nothing, pas faux dans Visual Basic (c'est le même que dans SQL).

pas jeter
la source
Pourquoi répondre à la question par essais et erreurs? Devrait être possible de le faire à partir des spécifications linguistiques.
David Heffernan
3
@DavidHeffernan, car cela montre la différence de langage qui est assez claire.
nothrow
2
@Yossarian Vous pensez que les spécifications linguistiques sont ambiguës sur la question. Je ne suis pas d'accord. L'IL est un détail d'implémentation sujet à changement; les spécifications ne le sont pas.
Servy
2
@DavidHeffernan: J'aime votre attitude et vous encourage à essayer. La spécification du langage VB peut parfois être difficile à analyser. Lucian l'améliore depuis quelques années maintenant, mais il peut encore être assez difficile de comprendre la signification exacte de ce type de cas d'angle. Je vous suggère d'obtenir une copie de la spécification, de faire des recherches et de rapporter vos conclusions.
Eric Lippert
2
@Yossarian Les résultats de l'exécution du code IL que vous avez fourni ne sont pas sujets à changement, mais le fait que le code C # / VB fourni sera compilé dans le code IL que vous avez montré est sujet à changement (tant que le comportement de cet IL est également conforme à la définition des spécifications linguistiques).
Servy
6

Le problème observé ici est un cas particulier d'un problème plus général, à savoir que le nombre de définitions différentes de l'égalité qui peuvent être utiles dans au moins certaines circonstances dépasse le nombre de moyens couramment disponibles pour les exprimer. Ce problème est dans certains cas aggravé par une malheureuse croyance qu'il est déroutant d'avoir différents moyens de tester l'égalité donnant des résultats différents, et une telle confusion pourrait être évitée en faisant en sorte que les différentes formes d'égalité donnent les mêmes résultats chaque fois que possible.

En réalité, la cause fondamentale de la confusion est une croyance erronée selon laquelle les différentes formes de tests d'égalité et d'inégalité devraient produire le même résultat, même si des sémantiques différentes sont utiles dans des circonstances différentes. Par exemple, d'un point de vue arithmétique, il est utile de pouvoir Decimalcomparer les valeurs qui ne diffèrent que par le nombre de zéros à la fin. De même pour des doublevaleurs telles que zéro positif et zéro négatif. D'un autre côté, du point de vue de la mise en cache ou de l'internement, une telle sémantique peut être mortelle. Supposons, par exemple, que l'on ait un Dictionary<Decimal, String>tel qui myDict[someDecimal]devrait égaler someDecimal.ToString(). Un tel objet semblerait raisonnable si l'on avait plusieursDecimalvaleurs que l'on voulait convertir en chaîne et s'attendait à ce qu'il y ait de nombreux doublons. Malheureusement, si une telle mise en cache était utilisée pour convertir 12,3 m et 12,40 m, suivis de 12,30 m et 12,4 m, ces dernières valeurs donneraient «12,3» et «12,40» au lieu de «12,30» et «12,4».

Pour en revenir à la question à l'étude, il existe plus d'une manière sensée de comparer des objets Nullable pour l'égalité. C # part du principe que son ==opérateur doit refléter le comportement de Equals. VB.NET part du principe que son comportement doit refléter celui de certains autres langages, car quiconque souhaite le Equalscomportement peut l'utiliser Equals. Dans un certain sens, la bonne solution serait d'avoir une construction "si" à trois voies et d'exiger que si l'expression conditionnelle renvoie un résultat à trois valeurs, le code doit spécifier ce qui doit se passer dans le nullcas. Étant donné que ce n'est pas une option avec les langues telles qu'elles sont, la meilleure alternative consiste simplement à apprendre comment les différentes langues fonctionnent et à reconnaître qu'elles ne sont pas les mêmes.

Incidemment, l'opérateur "Est" de Visual Basic, qui fait défaut dans C, peut être utilisé pour tester si un objet Nullable est, en fait, null. Bien que l'on puisse raisonnablement se demander si un iftest devrait accepter a Boolean?, avoir les opérateurs de comparaison normaux retournent Boolean?plutôt que Booleanlorsqu'ils sont appelés sur des types Nullable est une fonctionnalité utile. Incidemment, dans VB.NET, si l'on tente d'utiliser l'opérateur d'égalité plutôt que Is, on obtiendra un avertissement que le résultat de la comparaison sera toujours Nothing, et on devrait l'utiliser Issi l'on veut tester si quelque chose est nul.

supercat
la source
Tester si une classe est nulle en C # est effectué par == null. Et tester si un type de valeur Nullable a une valeur est effectué par .hasValue. À quoi sert un Is Nothingopérateur? C # a, ismais il teste la compatibilité des types. À la lumière de ces éléments, je ne sais vraiment pas ce que votre dernier paragraphe essaie de dire.
ErikE
@ErikE: vb.net et C # permettent de vérifier les types Nullable pour une valeur à l'aide d'une comparaison à null, bien que les deux langues traitent cela comme du sucre syntaxique pour une HasValuevérification, au moins dans les cas où le type est connu (je ne suis pas sûr quel code est généré pour les génériques).
supercat
Dans les génériques, vous pouvez avoir des problèmes délicats autour des types Nullables et de la résolution de surcharge ...
ErikE
3

Peut-être que cet article vous aidera:

Si je me souviens bien, «Rien» dans VB signifie «la valeur par défaut». Pour un type valeur, c'est la valeur par défaut, pour un type référence, ce serait null. Ainsi, n'attribuer rien à une structure ne pose aucun problème.

Evgenyl
la source
3
Cela ne répond pas à la question.
David Heffernan
Non, cela ne clarifie rien. La question est tout au sujet de l' <>opérateur dans VB, et comment il fonctionne sur les types Nullable.
David Heffernan
2

C'est une étrange bizarrerie de VB.

Dans VB, si vous souhaitez comparer deux types Nullable, vous devez utiliser Nullable.Equals().

Dans votre exemple, cela devrait être:

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
la source
5
C'est de la "bizarrerie" quand ce n'est pas familier. Voir la réponse de Pieter Geerkens.
rskar
Eh bien, je trouve aussi étrange que VB ne reproduise pas le comportement de Nullable<>.Equals(). On pourrait s'attendre à ce que cela fonctionne de la même manière (ce que fait C #).
Matthew Watson
Les attentes, comme ce à quoi «on pourrait s'attendre», concernent ce que l'on a vécu. C # a été conçu en tenant compte des attentes des utilisateurs Java. Java a été conçu en tenant compte des attentes des utilisateurs C / C ++. Pour le meilleur ou pour le pire, VB.NET a été conçu en tenant compte des attentes des utilisateurs de VB6. Plus de matière à réflexion sur stackoverflow.com/questions/14837209/… et stackoverflow.com/questions/10176737/…
rskar
1
@MatthewWatson La définition de Nullablen'existait pas dans les premières versions de .NET, elle a été créée après que C # et VB.NET soient sortis depuis un certain temps et ont déjà déterminé leur comportement de propagation nul. Pensez-vous honnêtement que le langage soit cohérent avec un type qui n'aura pas été créé avant plusieurs années? Du point de vue d'un programmeur VB.NET, c'est Nullable.Equals qui n'est pas cohérent avec le langage, plutôt que l'inverse. (Étant donné que C # et VB utilisent tous les deux la même Nullabledéfinition, il n'y avait aucun moyen pour que cela soit cohérent avec les deux langues.)
Servy
0

Votre code VB est tout simplement incorrect - si vous changez le "x <> y" en "x = y", vous aurez toujours "faux" comme résultat. La manière la plus courante d'exprimer ceci pour les instances Nullable est "Not x.Equals (y)", et cela donnera le même comportement que "x! = Y" en C #.

Dave Doknjas
la source
1
À moins que ce ne xsoit nothing, auquel cas x.Equals(y)lancera une exception.
Servy
@Servy: Je suis tombé dessus à nouveau (plusieurs années plus tard), et j'ai remarqué que je ne vous avais pas corrigé - "x.Equals (y)" ne lèvera pas d'exception pour l'instance de type nullable 'x'. Les types Nullable sont traités différemment par le compilateur.
Dave Doknjas
Plus précisément, une instance Nullable initialisée à «null» n'est pas vraiment une variable définie sur null, mais une instance System.Nullable sans valeur définie.
Dave Doknjas le