Laquelle est préférée: Nullable <T> .HasValue ou Nullable <T>! = Null?

437

J'ai toujours utilisé Nullable<>.HasValueparce que j'aimais la sémantique. Cependant, récemment, je travaillais sur la base de code existante de quelqu'un d'autre où ils l'utilisaient Nullable<> != nullexclusivement à la place.

Y a-t-il une raison de les utiliser l'un par rapport à l'autre, ou s'agit-il uniquement de préférence?

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

contre.

  1. int? b;
    if (b != null)
        // ...
lc.
la source
9
J'ai posé une question similaire ... a obtenu de bonnes réponses: stackoverflow.com/questions/633286/…
nailitdown
3
Personnellement , j'utiliserais HasValuecar je pense que les mots ont tendance à être plus lisibles que les symboles. Tout dépend de vous, et de ce qui correspond à votre style actuel.
Jake Petroules
1
.HasValueest plus logique car il indique que le type est de type T?plutôt qu'un type qui peut être annulable tel que des chaînes.
user3791372

Réponses:

476

Le compilateur remplace les comparaisons nulles par un appel à HasValue, il n'y a donc pas de réelle différence. Faites simplement ce qui est plus lisible / plus logique pour vous et vos collègues.

Rex M
la source
86
J'ajouterais à cela "ce qui est le plus cohérent / suit un style de codage existant."
Josh Lee
20
Sensationnel. Je déteste ce sucre syntaxique. int? x = nullme donne l'illusion qu'une instance nullable est un type de référence. Mais la vérité est que Nullable <T> est un type de valeur. Il se sent j'obtenir un NullReferenceException faire: int? x = null; Use(x.HasValue).
KFL
11
@KFL Si le sucre syntaxique vous dérange, utilisez simplement à la Nullable<int>place de int?.
Cole Johnson
24
Au début de la création d'une application, vous pourriez penser qu'il suffit d'utiliser un type de valeur nullable pour stocker certaines données, pour vous rendre compte au bout d'un moment que vous avez besoin d'une classe appropriée à votre objectif. Avoir écrit le code d'origine pour comparer avec null a alors l'avantage que vous n'avez pas besoin de rechercher / remplacer chaque appel à HasValue () par une comparaison nulle.
Anders
15
C'est assez idiot de se plaindre de pouvoir définir un Nullable sur null ou de le comparer à null étant donné que cela s'appelle Nullable . Le problème est que les gens confondent "type de référence" avec "peut être nul", mais c'est une confusion conceptuelle. Le futur C # aura des types de référence non nullables.
Jim Balter
48

Je préfère (a != null)que la syntaxe corresponde aux types de référence.

cbp
la source
11
Ce qui est bien sûr trompeur, car ce Nullable<>n'est pas un type de référence.
Luaan
9
Oui, mais le fait importe généralement très peu au moment où vous faites une vérification nulle.
cbp
31
C'est seulement trompeur pour le conceptuellement confus. L'utilisation d'une syntaxe cohérente pour deux types différents n'implique pas qu'ils sont du même type. C # a des types de référence nullables (tous les types de référence sont actuellement nullables, mais cela changera à l'avenir) et des types de valeurs nullables. L'utilisation d'une syntaxe cohérente pour tous les types nullables est logique. Cela n'implique nullement que les types de valeur nullable sont des types de référence, ou que les types de référence nullable sont des types de valeur.
Jim Balter
Je préfère HasValueparce que c'est plus lisible que!= null
Konrad
La cohérence du codage est plus lisible si vous ne mélangez pas différents styles d'écriture du même code. Étant donné que tous les espaces n'ont pas de propriété .HasValue, il est donc préférable d'utiliser! = Null pour une cohérence accrue. À mon avis.
ColacX
21

J'ai fait quelques recherches à ce sujet en utilisant différentes méthodes pour attribuer des valeurs à un entier nullable. Voici ce qui s'est passé lorsque j'ai fait diverses choses. Devrait clarifier ce qui se passe. Gardez à l'esprit: Nullable<something>ou le raccourci something?est une structure pour laquelle le compilateur semble faire beaucoup de travail pour nous permettre d'utiliser avec null comme s'il s'agissait d'une classe.
Comme vous le verrez ci-dessous, SomeNullable == nullet SomeNullable.HasValueretournera toujours un vrai ou un faux attendu. Bien que cela ne soit pas démontré ci-dessous, il SomeNullable == 3est également valide (en supposant que SomeNullable est un int?).
Alors que SomeNullable.Valuenous obtient une erreur d'exécution si nous avons attribué nullà SomeNullable. C'est en fait le seul cas où nullables pourrait nous poser problème, grâce à une combinaison d'opérateurs surchargés, surchargésobject.Equals(obj) méthode, et optimisation du compilateur et entreprise de singe.

Voici une description du code que j'ai exécuté et de la sortie qu'il a produite dans les étiquettes:

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.")

Ok, essayons la prochaine méthode d'initialisation:

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.")

Tout comme avant. Gardez à l'esprit que l'initialisation avec int? val = new int?(null);, avec null passé au constructeur, aurait produit une erreur de temps de compilation, car la valeur de l'objet nullable n'est PAS nullable. Seul l'objet wrapper lui-même peut être égal à null.

De même, nous obtiendrions une erreur de temps de compilation de:

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

sans compter que val.Valuec'est une propriété en lecture seule de toute façon, ce qui signifie que nous ne pouvons même pas utiliser quelque chose comme:

val.Value = 3;

mais encore une fois, les opérateurs de conversion implicites surchargés polymorphes nous permettent de faire:

val = 3;

Pas besoin de s'inquiéter de tout ce qui peut arriver, tant qu'il fonctionne bien? :)

Perrin Larson
la source
5
"Gardez à l'esprit: Nullable <quelque chose> ou le raccourci quelque chose? Est une classe." C'est faux! Nullable <T> est une structure. Il surcharge l'opérateur Equals et == pour renvoyer true par rapport à null. Le compilateur ne fait aucun travail de fantaisie pour cette comparaison.
andrewjs
1
@andrewjs - Vous avez raison de dire que c'est une structure (pas une classe), mais vous avez tort de surcharger l'opérateur ==. Si vous tapez Nullable<X>VisualStudio 2013 et F12, vous verrez qu'il surcharge uniquement la conversion vers et depuis Xet la Equals(object other)méthode. Cependant, je pense que l'opérateur == utilise cette méthode par défaut, donc l'effet est le même. En fait, je voulais mettre à jour cette réponse sur ce fait depuis un certain temps maintenant, mais je suis paresseux et / ou occupé. Ce commentaire devra faire pour l'instant :)
Perrin Larson
J'ai fait une vérification rapide via ildasm et vous avez raison sur le fait que le compilateur fait de la magie; la comparaison d'un objet Nullable <T> à null se traduit en fait par un appel à HasValue. Intéressant!
andrewjs
3
@andrewjs En fait, le compilateur fait une tonne de travail pour optimiser les nullables. Par exemple, si vous affectez une valeur à un type nullable, ce ne sera pas du tout nullable (par exemple, int? val = 42; val.GetType() == typeof(int)). Donc, non seulement est une structure nullable qui peut être égale à null, mais ce n'est souvent pas du tout une nullable! : D De la même manière, lorsque vous encadrez une valeur annulable, vous boxez int, non int?- et lorsque le int?n'a pas de valeur, vous obtenez à la nullplace d'une valeur annulable encadrée. Cela signifie essentiellement qu'il y a rarement des frais généraux pour utiliser correctement
nullable
1
@JimBalter Vraiment? C'est très intéressant. Que vous dit le profileur de mémoire sur un champ nullable dans une classe? Comment déclarez-vous un type de valeur qui hérite d'un autre type de valeur en C #? Comment déclarez-vous votre propre type nullable qui se comporte de la même manière que le type nullable de .NET? Depuis quand est Nullun type en .NET? Pouvez-vous indiquer la partie de la spécification CLR / C # où cela est dit? Les nullables sont bien définis dans la spécification CLR, leur comportement n'est pas une "implémentation d'une abstraction" - c'est un contrat . Mais si le mieux que vous puissiez faire est des attaques ad hominem, amusez-vous.
Luaan
13

Dans VB.Net. N'utilisez PAS "IsNot Nothing" lorsque vous pouvez utiliser ".HasValue". Je viens de résoudre une "Opération pourrait déstabiliser le runtime" Erreur de confiance moyenne en remplaçant "IsNot Nothing" par ".HasValue" à un endroit. Je ne comprends pas vraiment pourquoi, mais quelque chose se passe différemment dans le compilateur. Je suppose que "! = Null" en C # peut avoir le même problème.

Carter Medlin
la source
8
Je préfère en HasValueraison de la lisibilité. IsNot Nothingest vraiment une expression laide (à cause de la double négation).
Stefan Steinegger
12
@steffan "IsNot Nothing" n'est pas une double négation. "Rien" n'est pas un négatif, c'est une quantité discrète, même en dehors du domaine de la programmation. "Cette quantité n'est pas rien." est, grammaticalement, exactement la même chose que de dire "Cette quantité n'est pas nulle". et ni l'un ni l'autre n'est un double négatif.
jmbpiano
5
Ce n'est pas que je ne veux pas être en désaccord avec l'absence de vérité ici, mais allez maintenant. IsNot Rien n'est clairement, eh bien, trop négatif. Pourquoi ne pas écrire quelque chose de positif et de clair comme HasValue? Ce n'est pas un test de grammaire, c'est du codage, où l'objectif clé est la clarté.
Randy Gamage
3
jmbpiano: Je suis d'accord que ce n'est pas une double négation, mais c'est une seule négation et c'est presque aussi laid et pas aussi clair qu'une simple expression positive.
Kaveh Hadjari
0

Si vous utilisez linq et souhaitez garder votre code court, je recommande de toujours utiliser !=null

Et c'est pourquoi:

Imaginons que nous ayons une classe Fooavec une variable double nullableSomeDouble

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

Si, quelque part dans notre code, nous voulons obtenir tous les Foo avec des valeurs SomeDouble non nulles d'une collection de Foo (en supposant que certains foos de la collection peuvent également être nuls), nous nous retrouvons avec au moins trois façons d'écrire notre fonction (si nous utilisez 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
}

Et dans ce genre de situation, je recommande de toujours opter pour la plus courte

yan yankelevich
la source
2
Oui, foo?.SomeDouble.HasValueest une erreur de compilation (pas un "lancer" dans ma terminologie) dans ce contexte parce que son type n'est bool?pas juste bool. (La .Whereméthode veut un Func<Foo, bool>.) Il est permis de le faire (foo?.SomeDouble).HasValue, bien sûr, car cela a du type bool. C'est ce que votre première ligne est "traduite" en interne par le compilateur C # (au moins formellement).
Jeppe Stig Nielsen
-6

Réponse générale et règle générale: si vous avez une option (par exemple, écrire des sérialiseurs personnalisés) pour traiter Nullable dans un pipeline différent de object- et utiliser leurs propriétés spécifiques - faites-le et utilisez des propriétés spécifiques Nullable. Donc, d'un point de vue cohérent HasValuedevrait être préféré. Une réflexion cohérente peut vous aider à écrire un meilleur code sans passer trop de temps dans les détails. Par exemple, la deuxième méthode sera beaucoup plus efficace (principalement à cause des compilateurs en ligne et en boxe, mais les chiffres sont toujours très expressifs):

public static bool CheckObjectImpl(object o)
{
    return o != null;
}

public static bool CheckNullableImpl<T>(T? o) where T: struct
{
    return o.HasValue;
}

Test de référence:

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 |

Code de référence:

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 a été utilisé

PS . Les gens disent que les conseils "préfèrent HasValue en raison d'une pensée cohérente" ne sont pas liés et inutiles. Pouvez-vous prédire les performances de cela?

public static bool CheckNullableGenericImpl<T>(T? t) where T: struct
{
    return t != null; // or t.HasValue?
}

PPS Les gens continuent moins, mais personne n'essaie de prédire les performances de CheckNullableGenericImpl. Et le compilateur ne vous aidera pas à le remplacer !=nullpar HasValue. HasValuedoit être utilisé directement si vous êtes intéressé par les performances.

Roman Pokrovskij
la source
2
Vos CheckObjectImpl boîtes peuvent être annulées dans un object, alors qu'elles CheckNullableImpln'utilisent pas de boxe. La comparaison est donc très injuste. Non seulement il est parce que non prix, il est également inutile, comme il est indiqué dans la réponse acceptée , le compilateur réécritures !=à HasValuetoute façon.
GSerg
2
Les lecteurs n'ignorent pas la nature structurelle de Nullable<T>, vous le faites (en le mettant dans un object). Lorsque vous appliquez != nullavec un nullable sur la gauche, aucune boxe ne se produit car la prise en charge !=de nullables fonctionne au niveau du compilateur. C'est différent lorsque vous masquez le nullable du compilateur en le plaçant d'abord dans un object. Ni CheckObjectImpl(object o)votre point de repère n'ont de sens en principe.
GSerg
3
Mon problème est que je me soucie de la qualité du contenu de ce site Web. Ce que vous avez publié est trompeur ou faux. Si vous tentiez de répondre à la question du PO, votre réponse est fausse, ce qui est facile à prouver en remplaçant l' appel à CheckObjectImplson corps à l' intérieur CheckObject. Cependant, vos derniers commentaires révèlent que vous aviez en fait une question complètement différente à l'esprit lorsque vous avez décidé de répondre à cette question vieille de 8 ans, ce qui rend votre réponse trompeuse dans le contexte de la question d'origine. Ce n'était pas ce que le PO demandait.
GSerg
3
Mettez-vous à la place du prochain gars qui google what is faster != or HasValue. Il arrive à cette question, parcourt votre réponse, apprécie votre point de repère et dit: "Gee, je ne l'utiliserai jamais !=parce que c'est clairement tellement plus lent!" C'est une très mauvaise conclusion qu'il poursuivra ensuite en se répandant. C'est pourquoi je crois que votre réponse est nuisible - elle répond à une mauvaise question et pose donc une mauvaise conclusion au lecteur sans méfiance. Réfléchissez à ce qui se passe lorsque vous changez votre statut CheckNullableImplpour être égalementreturn o != null; .
GSerg
8
Je conteste votre réponse. Votre réponse semble trompeuse comme si elle montre la différence entre !=et HasValuealors qu'en fait elle montre la différence entre object oet T? o. Si vous faites ce que j'ai suggéré, c'est-à-dire, réécrivez CheckNullableImplcomme public static bool CheckNullableImpl<T>(T? o) where T: struct { return o != null; }, vous vous retrouverez avec une référence qui montre clairement que !=c'est beaucoup plus lent que !=. Ce qui devrait vous conduire à la conclusion que la question de votre réponse décrit est pas !=contre HasValuedu tout.
GSerg