C# 哪个是首选:Nullable<T>.HasValue 或 Nullable<T> != null?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/676078/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 13:00:40  来源:igfitidea点击:

Which is preferred: Nullable<T>.HasValue or Nullable<T> != null?

c#.netnullnullable

提问by lc.

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

我总是使用,Nullable<>.HasValue因为我喜欢语义。然而,最近我正在研究其他人的现有代码库,他们Nullable<> != null专门使用这些代码库。

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

是否有理由使用一个而不是另一个,还是纯粹是偏好?

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

vs.

对比

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

采纳答案by Rex M

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.

编译器用对 的调用替换空比较HasValue,因此没有真正的区别。只做对你和你的同事更易读/更有意义的事情。

回答by cbp

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

我更喜欢(a != null)语法匹配引用类型。

回答by Carter Medlin

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.

在 VB.Net 中。当您可以使用“.HasValue”时,请勿使用“IsNot Nothing”。我刚刚通过在一个地方用“.HasValue”替换“IsNot Nothing”解决了“操作可能破坏运行时的稳定性”中等信任错误。我真的不明白为什么,但在编译器中发生了不同的事情。我认为 C# 中的 "!= null" 可能有同样的问题。

回答by 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 == nulland SomeNullable.HasValuewill always return an expected true or false. Although not demonstrated below, SomeNullable == 3is valid too (assuming SomeNullable is an int?).
While SomeNullable.Valuegets us a runtime error if we assigned nullto 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.

我通过使用不同的方法将值分配给可为空的 int 对此进行了一些研究。这是我做各种事情时发生的事情。应该澄清发生了什么。请记住:Nullable<something>或者简写something?是一个结构体,编译器似乎为此做了很多工作,让我们像使用类一样使用 null。
正如您将在下面看到的,SomeNullable == null并且SomeNullable.HasValue将始终返回预期的 true 或 false。虽然下面没有演示,但SomeNullable == 3也是有效的(假设 SomeNullable 是int?)。
虽然SomeNullable.Value得到了我们一个运行时错误,如果我们分配nullSomeNullable。由于重载运算符的组合,这实际上是可空值可能给我们带来问题的唯一情况,重载object.Equals(obj)方法、编译器优化和猴子业务。

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.

一切都和以前一样。请记住,使用 初始化int? val = new int?(null);,并将 null 传递给构造函数,会产生编译时间错误,因为可空对象的 VALUE 不可为空。只有包装对象本身可以等于 null。

Likewise, we would get a compile time error from:

同样,我们会得到一个编译时错误:

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

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

更不用说val.Value无论如何这是一个只读属性,这意味着我们甚至不能使用类似的东西:

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

无需担心 polysomthing 是什么,只要它工作正常?:)

回答by Roman Pokrovskij

General answer and rule of thumb: 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 HasValueshould be preferred. Consistent thinking can help you to write better code do not spending too much time in details. E.g. there second method will be many times more effective (mostly because of compilers inlining and boxing but still numbers are very expressive):

一般答案和经验法则:如果您可以选择(例如编写自定义序列化程序)在不同的管道中处理 Nullable 而不是 object- 并使用它们的特定属性 - 这样做并使用 Nullable 特定属性。所以从一致的思路来看HasValue应该是首选。一致的思维可以帮助你写出更好的代码,不要在细节上花费太多时间。例如,第二种方法会更有效很多倍(主要是因为编译器内联和装箱,但数字仍然非常具有表现力):

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/BenchmarkDotNetwas used

https://github.com/dotnet/BenchmarkDotNet使用

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

附注。人们说建议“因为一致的想法而更喜欢 HasValue”是不相关和无用的。你能预测它的性能吗?

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

PPSPeople continue minus but nobody tries to predict performance of CheckNullableGenericImpl. And there compiler will not help you replacing !=nullwith HasValue. HasValueshould be used directly if you are interested in performance.

PPS人们继续负,但没有人试图预测CheckNullableGenericImpl. 而且编译器不会帮你替换!=nullHasValue. HasValue如果您对性能感兴趣,则应直接使用。

回答by yan yankelevich

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

如果您使用 linq 并希望保持代码简短,我建议您始终使用 !=null

And this is why:

这就是为什么:

Let imagine we have some class Foowith a nullable doublevariable SomeDouble

假设我们有Foo一个带有空双变量的类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) :

如果在我们的代码中的某个地方,我们想要从 Foo 的集合中获取所有具有非空 SomeDouble 值的 Foo(假设集合中的一些 foos 也可以为空),我们最终至少有三种方法来编写我们的函数(如果我们使用 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

在这种情况下,我建议总是选择较短的