ChatGPT解决这个技术问题 Extra ChatGPT

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

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?

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

vs.

int? b; if (b != null) // ...

I asked a similar question... got some good answers:stackoverflow.com/questions/633286/…
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.
.HasValue makes more sense as it denotes the type is of type T? rather than a type that can be nullable such as strings.

p
phoenix

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.


I would add to that "whichever is more consistent/follows an existing coding style."
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).
@KFL If the syntactic sugar bothers you, just use Nullable<int> instead of int?.
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.
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.
R
Roman Marusyk

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


Which is quite misleading, of course, since Nullable<> is not a reference type.
Yes, but the fact usually matters very little at the point you are null checking.
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.
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.
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.
P
Perrin Larson

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? :)


"Keep in mind: Nullable or the shorthand something? is a class." This is wrong! Nullable is a struct. It overloads Equals and == operator to return true when compared to null. The compiler does no fancy work for this comparison.
@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 :)
I did a quick check through ildasm and you are right about the compiler doing some magic; comparing a Nullable object to null does in fact translate to a call to HasValue. Interesting!
@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 :)
@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.
Y
Yifan Ai

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.


I would prefer HasValue because of readability. IsNot Nothing is really an ugly expression (because of the double negation).
@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.
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.
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.
y
yan yankelevich

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


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).
R
Roman Pokrovskij

There second method will be many times more effective (mostly because of compilers inlining and boxing but still numbers are very expressive):

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

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

Benchmark test:

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 |

Benchmark code:

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 was used

So if you have an option (e.g. writing custom serializers) to process Nullable in different pipeline than object - and use their specific properties - do it and use Nullable specific properties. So from consistent thinking point of view HasValue should be preferred. Consistent thinking can help you to write better code do not spending too much time in details.

PS. People say that advice "prefer HasValue because of consistent thinking" is not related and useless. Can you predict the performance of this?

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

PPS People continue minus, seems nobody tries to predict performance of CheckNullableGenericImpl. I will tell you: there compiler will not help you replacing !=null with HasValue. HasValue should be used directly if you are interested in performance.


Your CheckObjectImpl boxes the nullable into an object, whereas CheckNullableImpl does not use boxing. Thus the comparison is very unfare. Not only it is not fare, it is also useless because, as noted in the accepted answer, the compiler rewrites != to HasValue anyway.
The readers do not ignore struct nature of Nullable<T>, you do (by boxing it into an object). When you apply != null with a nullable on the left, no boxing occurs because the support for != for nullables works at the compiler level. It's different when you hide the nullable from the compiler by first boxing it into an object. Neither CheckObjectImpl(object o) nor your benchmark make sense in principle.
My problem is that I care about content quality on this website. What you posted is either misleading or wrong. If you were attempting to answer the OP's question, then your answer is flat out wrong, which is easy to prove by replacing the call to CheckObjectImpl with its body inside CheckObject. However your latest comments reveal that you in fact had a completely different question in mind when you decided to answer this 8 years old question, which makes your answer misleading in the context of the original question. It was not what the OP was asking about.
Put yourself in the shoes of the next guy who googles what is faster != or HasValue. He arrives at this question, browses through your answer, appreciates your benchmark and says, "Gee, I will never use != because it's clearly so much slower!" That is a very wrong conclusion which he will then proceed spreading around. That is why I believe your answer is harmful - it answers a wrong question and thus plants a wrong conclusion in the unsuspecting reader. Consider what happens when you change your CheckNullableImpl to also be return o != null; You will get the same benchmark result.
I am arguing with your answer. Your answer deceptively looks like it shows the difference between != and HasValue when in fact it shows the difference between object o and T? o. If you do what I suggested, that is, rewrite CheckNullableImpl as public static bool CheckNullableImpl<T>(T? o) where T: struct { return o != null; }, you will end up with a benchmark that clearly shows that != is much slower than !=. Which should lead you to conclusion that the issue your answer describes is not about != vs HasValue at all.