What is the difference between Nullable<T>.HasValue or Nullable<T> != null?
Asked Answered
S

5

531

I always used Nullable<>.HasValue because I liked the semantics. However, recently I was working on someone else's existing codebase where they used Nullable<> != null exclusively instead.

Is there a reason to use one over the other, or is it purely preference?

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

vs.

  1. int? b;
    if (b != null)
        // ...
    
Stieglitz answered 24/3, 2009 at 3:30 Comment(3)
I asked a similar question... got some good answers:#633786Herndon
Personally, I'd use HasValue since I think words tend to be more readable than symbols. It's all up to you though, and what fits with your existing style.Catechetical
.HasValue makes more sense as it denotes the type is of type T? rather than a type that can be nullable such as strings.Carrero
I
587

The compiler replaces null comparisons with a call to HasValue, so there is no real difference. Just do whichever is more readable/makes more sense to you and your colleagues.

Inexactitude answered 24/3, 2009 at 3:34 Comment(8)
I would add to that "whichever is more consistent/follows an existing coding style."Alphonsa
Wow. I hate this syntactic sugar. int? x = null gives me the illusion that a nullable instance is a reference type. But the truth is that Nullable<T> is a value type. It feels I'd get a NullReferenceException to do: int? x = null; Use(x.HasValue).Carmine
@Carmine If the syntactic sugar bothers you, just use Nullable<int> instead of int?.Zeal
In the early stages of creating an application you might think it's sufficent to use a nullable value type to store some data, only to realize after a while that you need a proper class for your purpose. Having written the original code to compare with null then has the advantage that you don't need to search/replace every call to HasValue() with a null comparison.Precritical
It's pretty silly to complain about being able to set a Nullable to null or compare it to null given that's called Nullable. The problem is that people are conflating "reference type" with "can be null", but that's a conceptual confusion. Future C# will have non-nullable reference types.Striper
@Carmine I sometimes use var z = x ?? y for nullable types. Would you still prefer var z = x.HasValue ? x : y in that case? Seems kinda weird.Naturalistic
@Naturalistic from KFL's example, it's the = null part that's kinda weird. If null were an instance of Nullable, so that you could expect null.HasValue() == false then it would be less weird that x.HasValue() works.Vitrescence
Is this also the case for Entity Framework LINQ expressions?Emporium
D
60

I prefer (a != null) so that the syntax matches reference types.

Devine answered 14/8, 2009 at 4:42 Comment(9)
Which is quite misleading, of course, since Nullable<> is not a reference type.Stephanestephani
Yes, but the fact usually matters very little at the point you are null checking.Devine
It's only misleading to the conceptually confused. Using a consistent syntax for two different types does not imply that they are the same type. C# has nullable reference types (all reference types are currently nullable, but that will change in the future) and nullable value types. Using a consistent syntax for all nullable types makes sense. In no way does it imply that nullable value types are reference types, or that nullable reference types are value types.Striper
I prefer HasValue because it's more readable than != nullAnimalcule
Coding consistance is more readable if you dont mix different styles of writing the same code. Since not all places have a .HasValue property then it makes since to use != null for increased consistancy. In my opinon.Mccallion
Definitely vote for this preference, if nothing else it makes coding changes simpler, as going from a reference type to a nullable type doesn't require code changes anywhere else, where using .HasValue becomes incorrect syntax as soon as it's no longer explicitly Nullable, which may not be a common case, but if you ever have written a struct for Tuple's sake, and then turned it into a class, you've been in the area that this applies, and with NullableRefs coming up, this will become much more likely to occur.Detrain
In cases where you access .Value it makes sense to use .HasValue though.Emporium
What happens if anybody creates a custom equality operator (== or !=)? I could write my own operator so that var a = new A(); a == null is true. Not that any sane person would do that but it is possible. HasValue doesn't run custom equality operators as far as I'm aware.Uranic
In that case, you find the person who did it and get them the help they need.Devine
D
23

I did some research on this by using different methods to assign values to a nullable int. Here is what happened when I did various things. Should clarify what's going on. Keep in mind: Nullable<something> or the shorthand something? is a struct for which the compiler seems to be doing a lot of work to let us use with null as if it were a class.
As you'll see below, SomeNullable == null and SomeNullable.HasValue will always return an expected true or false. Although not demonstrated below, SomeNullable == 3 is valid too (assuming SomeNullable is an int?).
While SomeNullable.Value gets us a runtime error if we assigned null to SomeNullable. This is in fact the only case where nullables could cause us a problem, thanks to a combination of overloaded operators, overloaded object.Equals(obj) method, and compiler optimization and monkey business.

Here is a description of some code I ran, and what output it produced in labels:

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, lets try the next initialization method:

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

All the same as before. Keep in mind that initializing with int? val = new int?(null);, with null passed to the constructor, would have produced a COMPILE time error, since the nullable object's VALUE is NOT nullable. It is only the wrapper object itself that can equal null.

Likewise, we would get a compile time error from:

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

not to mention that val.Value is a read-only property anyway, meaning we can't even use something like:

val.Value = 3;

but again, polymorphous overloaded implicit conversion operators let us do:

val = 3;

No need to worry about polysomthing whatchamacallits though, so long as it works right? :)

Demoniac answered 11/4, 2014 at 18:23 Comment(9)
"Keep in mind: Nullable<something> or the shorthand something? is a class." This is wrong! Nullable<T> is a struct. It overloads Equals and == operator to return true when compared to null. The compiler does no fancy work for this comparison.Luralurch
@andrewjs - You are right that it's a struct (not a class), but you are wrong that it overloads the == operator. If you type Nullable<X> in VisualStudio 2013 and F12 it, you will see that it only overloads conversion to and from X, and the Equals(object other) method. However, I think the == operator uses that method by default, so the effect is the same. I've actually been meaning to update this answer on that fact for a while now, but I'm lazy and/or busy. This comment will have to do for now :)Demoniac
I did a quick check through ildasm and you are right about the compiler doing some magic; comparing a Nullable<T> object to null does in fact translate to a call to HasValue. Interesting!Luralurch
@andrewjs Actually, the compiler does a ton of work to optimize nullables. For example, if you assign a value to a nullable type, it will not actually be a nullable at all (e.g., int? val = 42; val.GetType() == typeof(int)). So not only is nullable a struct that can be equal to null, it also often isn't a nullable at all! :D The same way, when you box a nullable value, you're boxing int, not int? - and when the int? doesn't have a value, you get null instead of a boxed nullable value. It basically means there's rarely any overhead from using nullable properly :)Stephanestephani
"Without help from the compiler, trying to call HasValue on a Nullable whose variable was assigned null would produce a null reference runtime error." -- This is nonsense. Nullable<int> is a value type, not a reference type, and so it can't possibly get a null reference error. A number of comments on this page reflect this confusion, conflating "nullable" with "reference type".Striper
@Stephanestephani "it will not actually be a nullable at all" -- this and the rest of your comment is nonsense. Types are nullable; values aren't. A nullable type T? can be either a T or null. Of course when it's a T, GetType() returns typeof(T), because GetType yields the runtime type. "there's rarely any overhead" -- of course there is overhead; extra storage is needed for the flag that says whether the value is null, and runtime checks are required to test that flag.Striper
@JimBalter Okay, so tell me how a value type can have a different runtime type from a compile-time type without the runtime/compiler cheating. Can you make your own nullable type that would behave the same way? Can you overload the boxing operator for a value type? How do you overload GetType? What overhead do you get from Nullable.GetValueOrDefault? What overhead do you get from boxing a nullable type on top of the boxing of any other value type? The only case where you get overhead is when not boxing (the flag) and when checking for null (that's what you wanted!).Stephanestephani
@Stephanestephani A variable with int? compile-time type has a runtime type of either int or Null. It isn't "cheating", it's an implementation of an abstraction. A variable with Base compile-time type can have Derived as runtime type -- no "cheating". This is very basic.Striper
@JimBalter Really? That's very interesting. So what does the memory profiler tell you about a nullable field in a class? How do you declare a value type that inherits from another value type in C#? How do you declare your own nullable type that behaves the same as .NET's nullable type? Since when is Null a type in .NET? Can you point to the part in the CLR/C# specification where that's said? Nullables are well defined in the CLR specification, their behaviour is no "implementation of an abstraction" - it's a contract. But if the best you can do is ad hominem attacks, enjoy yourself.Stephanestephani
C
15

In VB.Net, do NOT use IsNot Nothing when you can use .HasValue. I just solved an "Operation could destabilize the runtime" Medium trust error by replacing IsNot Nothing with .HasValue in one spot. I don't really understand why, but something is happening differently in the compiler. I would assume that != null in C# may have the same issue.

Ciliata answered 23/4, 2010 at 18:13 Comment(4)
I would prefer HasValue because of readability. IsNot Nothing is really an ugly expression (because of the double negation).Ranger
@steffan "IsNot Nothing" isn't double negation. "Nothing" isn't a negative, it's a discrete quantity, even outside the realm of programming. "This quantity is not nothing." is, grammatically, the exact same as saying "This quantity is not zero." and neither is a double negative.Lexicostatistics
It's not that I don't want to disagree with the absence of truth here, but come on now. IsNot Nothing is clearly, well, overly negative. Why not write something positive and clear like HasValue? This is not a grammar test, it's coding, where the key objective is clarity.Askari
jmbpiano: I agree it's not double negation, but it's a single negation and that's almost as ugly and not as clear as a simple positive expression.Lateral
S
0

If you use linq and want to keep your code short, I recommand to always use !=null

And this is why:

Let imagine we have some class Foo with a nullable double variable SomeDouble

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

If somewhere in our code we want to get all Foo with a non null SomeDouble values from a collection of Foo (assuming some foos in the collection can be null too), we end up with at least three way to write our function (if we use 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
}

And in this kind of situation I recommand to always go for the shorter one

Sheree answered 28/8, 2017 at 14:38 Comment(1)
Yes, foo?.SomeDouble.HasValue is a compile-time error (not a "throw" in my terminology) in that context because its type is bool?, not just bool. (The .Where method wants a Func<Foo, bool>.) It is allowed to do (foo?.SomeDouble).HasValue, of course, since that has type bool. This is what your first line is "translated" into internally by the C# compiler (at least formally).Awestricken

© 2022 - 2024 — McMap. All rights reserved.