【问题标题】:What is the difference between Nullable<T>.HasValue or Nullable<T> != null?Nullable<T>.HasValue 或 Nullable<T> != null 有什么区别?
【发布时间】:2010-10-15 03:13:26
【问题描述】:

我一直使用Nullable&lt;&gt;.HasValue,因为我喜欢它的语义。但是,最近我正在研究其他人的现有代码库,他们只使用了Nullable&lt;&gt; != null

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

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

对比

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

【问题讨论】:

  • 我问了一个类似的问题...得到了一些很好的答案:stackoverflow.com/questions/633286/…
  • 就个人而言,我会使用HasValue,因为我认为文字往往比符号更易读。不过,这完全取决于您,以及适合您现有风格的方式。
  • .HasValue 更有意义,因为它表示类型是T? 类型,而不是可以为空的类型,例如字符串。

标签: c# .net null nullable


【解决方案1】:

编译器将null 的比较替换为对HasValue 的调用,因此没有真正的区别。只做对您和您的同事更易读/更有意义的那个。

【讨论】:

  • 我会补充说“哪个更一致/遵循现有的编码风格。”
  • 哇。我讨厌这种语法糖。 int? x = null 给我一种可以为空的实例是引用类型的错觉。但事实是 Nullable 是一种值类型。感觉我会得到一个 NullReferenceException 来做:int? x = null; Use(x.HasValue)
  • @KFL 如果语法糖困扰您,只需使用Nullable&lt;int&gt; 而不是int?
  • 在创建应用程序的早期阶段,您可能认为使用可为空的值类型来存储一些数据就足够了,但过了一段时间才意识到您需要一个合适的类来满足您的目的。编写原始代码以与 null 进行比较后,您无需使用 null 比较搜索/替换对 HasValue() 的每个调用。
  • 抱怨能够将 Nullable 设置为 null 或将其与 null 进行比较是非常愚蠢的,因为它称为 Nullable。问题是人们将“引用类型”与“可以为空”混为一谈,但这是概念上的混淆。未来的 C# 将具有不可为空的引用类型。
【解决方案2】:

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

【讨论】:

  • 当然,这很容易引起误解,因为Nullable&lt;&gt; 不是引用类型。
  • 是的,但在您进行空值检查时,这一事实通常无关紧要。
  • 这只会误导概念混乱的人。对两种不同类型使用一致的语法并不意味着它们是相同的类型。 C# 具有可为空的引用类型(所有引用类型当前都是可为空的,但将来会改变)和可为空的值类型。对所有可空类型使用一致的语法是有意义的。这绝不意味着可空值类型是引用类型,或者可空引用类型是值类型。
  • 如果您不混合使用不同风格编写相同的代码,则编码一致性更具可读性。由于并非所有地方都有 .HasValue 属性,因此使用 != null 来增加一致性。在我看来。
  • 绝对投票支持这个偏好,如果没有别的,它会使编码更改更简单,因为从引用类型到可为空的类型不需要在其他任何地方更改代码,使用 .HasValue 变得不正确的语法一旦它不再显式地Nullable,这可能不是一个常见的情况,但是如果你曾经为了元组的缘故编写过一个结构,然后把它变成一个类,你就已经进入了这个适用的领域,随着 NullableRefs 的出现,这将变得更有可能发生。
【解决方案3】:

我通过使用不同的方法将值分配给可为空的 int 对此进行了一些研究。这是我做各种事情时发生的事情。应该澄清发生了什么。 请记住:Nullable&lt;something&gt; 或简写 something? 是一个结构,编译器似乎为此做了很多工作,让我们可以像使用类一样使用 null。
正如您将在下面看到的,SomeNullable == nullSomeNullable.HasValue 将始终返回预期的真或假。尽管下面没有演示,SomeNullable == 3 也是有效的(假设 SomeNullable 是 int?)。
如果我们将null 分配给SomeNullableSomeNullable.Value 会给我们带来运行时错误。事实上,由于重载运算符、重载 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 传递给构造函数会产生编译时错误,因为可空对象的 VALUE 不可为空。只有包装对象本身可以等于 null。

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

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

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

val.Value = 3;

但同样,多态重载隐式转换运算符让我们这样做:

val = 3;

只要能正常工作,就不必担心什么chamacallits 的多态性? :)

【讨论】:

  • “记住:Nullable 或简写的东西?是一个类。”这是错误的! Nullable 是一个结构。它重载 Equals 和 == 运算符以在与 null 比较时返回 true。编译器并没有为这种比较做任何花哨的工作。
  • @andrewjs - 你是对的,它是一个结构(不是一个类),但你错了,它重载了 == 运算符。如果您在 VisualStudio 2013 中键入 Nullable&lt;X&gt; 并按 F12,您将看到它只会重载与 XEquals(object other) 方法的转换。但是,我认为 == 运算符默认使用该方法,因此效果是一样的。我实际上一直想更新这个关于这个事实的答案一段时间,但我很懒和/或很忙。这个评论现在必须做:)
  • 我通过 ildasm 做了一个快速检查,你说的编译器做了一些魔法是对的;将 Nullable 对象与 null 进行比较实际上会转化为对 HasValue 的调用。有趣!
  • @andrewjs 实际上,编译器做了很多工作来优化可空值。例如,如果您将值分配给可空类型,则它实际上根本不是可空的(例如,int? val = 42; val.GetType() == typeof(int))。因此,不仅可以为 null 的结构可以等于 null,而且通常根本不是可以为 null 的! :D 同样,当你装箱一个可为空的值时,你装箱的是int,而不是int? - 当int? 没有值时,你得到null 而不是装箱的可为空值.这基本上意味着正确使用可空值很少有任何开销:)
  • @JimBalter 真的吗?这很有趣。那么内存分析器会告诉您关于类中可为空的字段的哪些信息?如何在 C# 中声明从另一个值类型继承的值类型?如何声明自己的可空类型,其行为与 .NET 的可空类型相同?从什么时候Null 成为.NET 中的类型?您能否指出 CLR/C# 规范中所说的部分?可空对象在 CLR 规范中得到了很好的定义,它们的行为不是“抽象的实现”——它是一个契约。但是,如果您能做的最好的事情就是人身攻击,那就尽情享受吧。
【解决方案4】:

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

【讨论】:

  • 我更喜欢 HasValue 因为可读性。 IsNot Nothing 真是一个丑陋的表达(因为双重否定)。
  • @steffan "IsNot Nothing" 不是双重否定。 “无”不是负数,它是一个离散量,即使在编程领域之外。 “这个数量不是什么。”从语法上讲,与说“这个数量不是零”完全相同。也不是双重否定。
  • 我并不是不想不同意这里没有真相,而是现在来吧。 IsNotNothing 显然,嗯,过于消极。为什么不写一些像 HasValue 这样积极而清晰的东西呢?这不是语法测试,而是编码,主要目标是清晰。
  • jmbpiano:我同意这不是双重否定,但它是一个单一的否定,这几乎和简单的积极表达一样丑陋和清晰。
【解决方案5】:

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

这就是原因:

假设我们有一个类Foo 带有一个可为空的双精度 变量SomeDouble

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

如果在我们的代码中的某个地方,我们想从 Foo 集合中获取所有具有非 null SomeDouble 值的 Foo(假设集合中的某些 foo 也可以为 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
}

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

【讨论】:

  • 是的,foo?.SomeDouble.HasValue 在这种情况下是一个编译时错误(在我的术语中不是“抛出”),因为它的类型是bool?,而不仅仅是bool。 (.Where 方法需要一个Func&lt;Foo, bool&gt;。)当然,它可以使用(foo?.SomeDouble).HasValue,因为它的类型为bool。这就是 C# 编译器在内部将第一行“翻译”成的内容(至少形式上如此)。
【解决方案6】:

第二种方法的效果会好很多倍(主要是因为编译器内联和装箱,但数字仍然很有表现力):

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

因此,如果您有一个选项(例如,编写自定义序列化程序)在与 object 不同的管道中处理 Nullable - 并使用它们的特定属性 - 执行此操作并使用 Nullable 特定属性。 所以从一致的思维角度来看HasValue应该是首选。一致的思维可以帮助您编写更好的代码,而不是在细节上花费太多时间。

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

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

PPS 人们继续减负,似乎没有人试图预测CheckNullableGenericImpl 的表现。我会告诉你:那里的编译器不会帮助你用HasValue 替换!=null。对性能感兴趣的可以直接使用HasValue

【讨论】:

  • 您的 CheckObjectImpl boxes 可以为空转换为 object,而 CheckNullableImpl 不使用装箱。因此,比较是非常不公平的。它不仅不是票价,而且也没用,因为正如accepted answer 中所述,编译器无论如何都会将!= 重写为HasValue
  • 读者不会忽略Nullable&lt;T&gt; 的结构性质,你会这样做(通过将其装箱成object)。当您在左侧应用带有可空值的!= null 时,不会发生装箱,因为对可空值的!= 的支持在编译器级别起作用。当您通过首先将它装箱到object 中来隐藏编译器的可空值时,情况会有所不同。原则上,CheckObjectImpl(object o) 和您的基准测试都没有意义。
  • 我的问题是我关心这个网站的内容质量。您发布的内容要么具有误导性,要么是错误的。如果您试图回答 OP 的问题,那么您的答案完全是错误的,这很容易通过将 CheckObjectImplcall 替换为 @ 内的 body 来证明987654346@。但是,您最新的 cmets 显示,当您决定回答这个 8 年前的问题时,您实际上有一个完全不同的问题,这使您的回答在原始问题的上下文中具有误导性。这不是 OP 所要求的。
  • 把自己放在下一个用谷歌搜索 what is faster != or HasValue 的人的位置上。他提出这个问题,浏览您的答案,欣赏您的基准并说:“哎呀,我永远不会使用!=,因为它显然要慢得多!”这是一个非常错误的结论,然后他将继续传播。这就是为什么我认为你的回答是有害的——它回答了一个错误的问题,从而在毫无戒心的读者中植入了一个错误的结论。考虑一下当您将 CheckNullableImpl 更改为 alsoreturn o != null; 时会发生什么,您将获得相同的基准测试结果。
  • 我在争论你的回答。您的回答看似显示了!=HasValue 之间的区别,但实际上它显示了object oT? o 之间的区别。如果您按照我的建议进行操作,即将CheckNullableImpl 重写为public static bool CheckNullableImpl&lt;T&gt;(T? o) where T: struct { return o != null; },您最终会得到一个基准,清楚地表明!=!= 慢得多。这应该使您得出结论,您的答案描述的问题根本与!=HasValue 无关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-04-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多