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


437

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

是否有理由优先使用一个,还是纯粹是偏爱?

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

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

9
我问过类似的问题...得到了一些很好的答案:stackoverflow.com/questions/633286/...
nailitdown

3
个人会使用,HasValue因为我认为单词比符号更易读。这完全取决于您,什么适合您现有的样式。
杰克·彼得鲁里斯

1
.HasValue更加有意义,因为它表示类型是类型,T?而不是可以为空的类型(例如字符串)。
user3791372

Answers:


476

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


86
我要补充一点,“以更一致的方式/遵循现有的编码样式。”
乔什·李

20
哇。我讨厌这种语法糖。int? x = null给我一种幻觉,即可为空的实例是引用类型。但事实是Nullable <T>是一个值类型。我感觉我会得到一个NullReferenceException做:int? x = null; Use(x.HasValue)
KFL

11
@KFL如果语法糖困扰您,请使用Nullable<int>代替int?
科尔·约翰逊

24
在创建应用程序的早期阶段,您可能会认为使用可为空的值类型存储一些数据就足够了,但过一会儿才意识到您需要一个适合自己目的的类。编写原始代码以与null进行比较具有以下优点:您无需使用null比较来搜索/替换对HasValue()的每次调用。
Anders

15
抱怨能够将Nullable设置为null或将其与null进行比较(这被称为Nullable)是很愚蠢的。问题在于人们正在将“引用类型”与“可以为空”进行混淆,但这是一个概念上的混乱。将来的C#将具有不可为空的引用类型。
Jim Balter

48

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


11
这是相当误导,当然,既然Nullable<>不是引用类型。
罗安2015年

9
是的,但是在您进行空值检查时,事实通常无关紧要。
cbp

31
这只会误导概念上的混乱。对两种不同类型使用一致的语法并不意味着它们是同一类型。C#具有可为空的引用类型(所有引用类型当前为可为空,但是将来会更改)和可为空的值类型。对所有可为空的类型使用一致的语法是有意义的。绝不暗示可为空的值类型是引用类型,或可为空的引用类型是值类型。
2015年

我更喜欢,HasValue因为它比!= null
Konrad

如果您不混合编写同一代码的不同样式,则代码一致性更易于阅读。由于并非所有位置都具有.HasValue属性,因此使用!= null可以提高一致性。在我看来。
ColacX

21

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

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

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

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

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传递给构造函数,这会产生COMPILE时间错误,因为可为空的对象的VALUE不可为空。只有包装对象本身可以等于null。

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

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

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

val.Value = 3;

但是同样,多态重载的隐式转换运算符使我们能够:

val = 3;

只要它正常工作,就不用担心杂乱的东西。:)


5
“请记住:Nullable <something>还是速记的东西?是一类。” 错了!Nullable <T>是一个结构。与null相比,它会重载Equals和==运算符以返回true。编译器对此比较没有花哨的工作。
andrewjs 2015年

1
@andrewjs-你是对的,它是一个结构体(不是类)是正确的,但是它重载了==运算符是错误的。如果您键入Nullable<X>VisualStudio 2013和F12,您将看到它只会重载往返于XEquals(object other)方法之间的转换。但是,我认为==运算符默认情况下使用该方法,因此效果是相同的。实际上,我已经有一段时间要根据这个事实更新此答案了,但是我很懒和/或很忙。该评论现在必须做:)
Perrin Larson 2015年

我对ildasm进行了快速检查,您对编译器做一些魔术是正确的;将Nullable <T>对象与null进行比较实际上会转换为对HasValue的调用。有趣!
andrewjs

3
@andrewjs实际上,编译器做了很多工作来优化可空值。例如,如果您将值分配为可为null的类型,则实际上根本不会是可为null的值(例如int? val = 42; val.GetType() == typeof(int))。因此,不仅可以为null的结构可以等于null,而且通常也不是完全可以为null!:D用同样的方法,当您将一个可为空的值装箱时,您是在装箱int,而不是int?-当int?它没有一个值时,您将得到null而不是装箱的可为空值。这基本上意味着正确使用nullable几乎没有任何开销:)
Luaan 2015年

1
@JimBalter真的吗?那很有趣。那么,内存探查器会告诉您有关类中可为空的字段的信息?如何声明从C#中的另一个值类型继承的值类型?您如何声明自己的行为与.NET的nullable类型相同?从什么时候开始Null.NET中的类型?您可以指出CLR / C#规范中的那部分吗?可空变量在CLR规范中有很好的定义,它们的行为不是“抽象的实现”,而是一种契约。但是,如果您能做的最好的就是人为攻击,那就尽情享受吧。
a安

13

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


8
HasValue由于可读性,我更喜欢。IsNot Nothing这确实是一个丑陋的表达(由于双重否定)。
Stefan Steinegger

12
@steffan“什么都不是”不是双重否定。“无”不是负数,即使在编程领域之外,也是离散的数量。“这个数量不算什么。” 在语法上与“这个数量不为零”完全相同。也不是双重否定的。
jmbpiano 2015年

5
这并不是说我不想不同意这里没有真理,而是现在就来。IsNot Nothing显然是非常消极的。为什么不写像HasValue这样积极而明确的东西?这不是语法测试,而是编码,其主要目标是清晰度。
兰迪·伽米奇

3
jmbpiano:我同意这不是双重否定,而是单一否定,几乎是丑陋的,而且不如简单的正面表达那么清晰。
卡夫

0

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

这就是为什么:

假设我们有Foo一个带有null的double变量的类SomeDouble

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

如果我们想在代码中的某个地方从Foo集合中获取所有具有非null SomeDouble值的Foo(假设该集合中的某些foos也可以为null),那么我们将以至少三种方式编写函数(如果我们使用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
}

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


2
是的,foo?.SomeDouble.HasValue在那种情况下是编译时错误(在我的术语中不是“抛出”),因为它的类型是bool?,而不仅仅是bool。(该.Where方法需要一个Func<Foo, bool>。)(foo?.SomeDouble).HasValue当然可以这样做,因为它具有type bool。这就是C#编译器在内部(至少在形式上)将第一行“翻译”成的内容。
Jeppe Stig Nielsen

-6

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

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

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

基准测试:

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 |

基准代码:

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使用

PS。人们说建议“由于始终如一的想法而首选HasValue”是无关的和无用的。您可以预测其性能吗?

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

PPS人们继续减负,但是没有人试图预测PPS的表现CheckNullableGenericImpl。而且那里的编译器不会帮助您替换!=nullHasValueHasValue如果您对性能感兴趣,应直接使用。


2
您的可为空的CheckObjectImpl 会变成object,而CheckNullableImpl不会使用装箱。因此,比较是不公平的。它不仅不加价,这也是无用的,因为,正如指出的接受的答案,编译器重写!=HasValue反正。
Gserg '17

2
读者不会忽略的结构性质Nullable<T>您可以这样做(通过将其装箱到中object)。当您!= null在左侧使用nullable进行应用时,不会出现任何装箱,因为!=对nullable 的支持在编译器级别起作用。当您通过将第一个框放入时,向编译器隐藏可空值时,情况有所不同objectCheckObjectImpl(object o)从原则上讲,您的基准都没有意义。
Gserg

3
我的问题是我关心此网站上的内容质量。您发布的内容具有误导性或错误性。如果你试图回答的任择议定书的问题,那么你的答案是平出错误的,这是很容易通过更换证明调用CheckObjectImpl与它的身体CheckObject。但是,您的最新评论表明,当您决定回答这个已有8年历史的问题时,实际上您想到的是一个完全不同的问题,这会使您的回答在原始问题的背景下产生误导。这不是OP的要求。
Gserg '17

3
让自己成为下一个谷歌搜索引擎的人的鞋子what is faster != or HasValue。他得出了这个问题,浏览了您的答案,赞赏您的基准测试,并说:“ Gee,我永远不会使用,!=因为它明显慢得多!” 这是一个非常错误的结论,他随后将继续散布。这就是为什么我认为您的答案有害的原因-它回答了错误的问题,因此在毫无戒心的读者中提出了错误的结论。想想当你改变你发生了什么CheckNullableImpl可以return o != null;,你会得到同样的基准测试结果。
Gserg

8
我在跟你的答案争论。您的答案看起来像是显示了!=和之间的差异,而HasValue实际上却显示了object o和之间的差异T? o。如果按照我的建议进行操作,即改写CheckNullableImplpublic static bool CheckNullableImpl<T>(T? o) where T: struct { return o != null; },则最终会得到一个基准测试,该基准清楚地表明它!=比慢得多!=。这应该使您得出结论,您的答案所描述的问题!=与根本无关HasValue
Gserg '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.