关于c#:哪个是优选的:Nullable< T> .HasValue或Nullable< T>

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

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

是否有理由使用其中一个而不是另一个,或者它纯粹是偏好?

  • 1
    2
    3
    int? a;
    if (a.HasValue)
        // ...
  • VS

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

  • 编译器将空比较替换为对EDOCX1的调用(0),因此没有真正的区别。只要做你和你的同事更易读/更有意义的事情。


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


    我用不同的方法为一个可以为空的int赋值,对此做了一些研究。下面是我做各种事情时发生的事情。应该弄清楚发生了什么。请记住:Nullable或简写something?是一个结构,编译器似乎正在为它做大量工作,以便让我们像使用类一样使用空值。
    正如您将在下面看到的,SomeNullable == nullSomeNullable.HasValue将始终返回预期的正确或错误。虽然下面没有演示,但SomeNullable == 3也是有效的(假设someNullable是int?
    而如果我们将null分配给SomeNullableSomeNullable.Value会给我们一个运行时错误。事实上,这是唯一一个可以为空值的情况,因为重载的操作符、重载的object.Equals(obj)方法、编译器优化和monkey业务的组合会导致我们出现问题。

    下面是我运行的一些代码的描述,以及它在标签中生成的输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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.")

    好,让我们尝试下一个初始化方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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.")

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

    同样,我们将从以下位置得到编译时错误:

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

    更不用说val.Value是只读属性,这意味着我们甚至不能使用如下内容:

    1
    val.Value = 3;

    但是,多形重载隐式转换运算符让我们可以这样做:

    1
    val = 3;

    不必担心一词多义,不过,只要它正确就行了。:)


    在VB.Net。如果可以使用".hasValue",请不要使用"isnot nothing"。我刚刚解决了一个"操作可能会破坏运行时"中的信任错误,将"isnot nothing"替换为".hasValue"。我真的不明白为什么,但是编译器中发生了不同的事情。我假设"!C中的"=null"可能有相同的问题。


    如果您使用LINQ并且希望保持代码简短,我建议您总是使用!=null

    这就是为什么:

    假设我们有一个类Foo,它有一个可以为空的双变量SomeDouble

    1
    2
    3
    4
    5
    public class Foo
    {
        public double? SomeDouble;
        //some other properties
    }

    如果在我们的代码中的某个地方,我们希望从foo集合中获取具有非空somedouble值的所有foo(假设集合中的某些foo也可以为空),那么最后我们至少有三种方法来编写函数(如果我们使用c 6):

    1
    2
    3
    4
    5
    6
    7
    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
    }

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


    一般的回答和经验法则:如果您有一个选项(例如,编写自定义序列化程序)来处理与object不同的管道中的可空值,并使用它们的特定属性,那么就执行该操作并使用可空的特定属性。因此,从一致的思维角度来看,应该优先选择HasValue。一致的思想可以帮助您编写更好的代码,不要在细节上花费太多时间。例如,第二种方法的效率要高出许多倍(主要是因为编译器的内联和装箱,但数字仍然很有表现力):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static bool CheckObjectImpl(object o)
    {
        return o != null;
    }

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

    基准测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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 |

    基准代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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

    人们说,建议"因为始终如一的思考而偏爱有价值的东西"是不相关和无用的。你能预测这个的性能吗?

    1
    2
    3
    4
    public static bool CheckNullableGenericImpl<T>(T? t) where T: struct
    {
        return t != null;
    }

    PPS继续下降,但没有人试图预测CheckNullableGenericImpl的表现。而且编译器不会帮助您用HasValue替换!=null。如果您对性能感兴趣,可以直接使用HasValue