【问题标题】:Is "ReferenceEquals(myObject, null)" better practice than "myObject == null"?“ReferenceEquals(myObject,null)”比“myObject == null”更好吗?
【发布时间】:2012-09-10 01:33:29
【问题描述】:

我有一位同事喜欢编写如下的空检查:

if (!ReferenceEquals(myObject, null))

另一方面,我觉得这种语法阅读起来很麻烦,而且更喜欢:

if (myObject != null)

我发现一些文章和堆栈溢出问题讨论了 ReferenceEquals 在运算符重载方面的优点,但在运算符重载场景之外,ReferenceEquals 与 == 有什么好处?

【问题讨论】:

  • 值得注意的是,您朋友的版本可读性较差(当支票可以互换时,这会成为一个问题,通常是这样)。
  • 另外 - 比上面更糟糕 - 你必须输入 if (!Object.ReferenceEquals(myObject, null)) - 所以它比实际描述的还要长。
  • @ReedCopsey 不,您不必键入,因为您的方法位于从其(直接或间接)基类 System.Object 继承静态 ReferenceEquals 方法的类或结构中.所以按照上面的写法是可以的(用new方法隐藏继承的方法当然可能会发生,但这是一个邪恶的例外)。
  • @ReedCopsey 好吧,您可以轻松判断 ReferenceEquals 是否隐藏(或者定义了另一个匹配的重载)。但是,如果要遵循您的理念,可以说 global::System.Object.ReferenceEquals(myObject, null),因为假设 somone 定义了其他类型或命名空间,称为 ObjectSystem ...
  • @JeppeStigNielsen 或者只使用“object.Ref..”作为语言定义的。不过,我个人的理念是使用 == :)

标签: c# .net vb.net


【解决方案1】:

但在运算符重载场景之外,ReferenceEquals vs == 有什么好处吗?

不——显式使用Object.ReferenceEquals 的唯一优势(我认为这不是什么优势)是它永远不会使用重载的运算符equals。在非重载的情况下,== Operator 被定义为“如果它的两个操作数引用同一个对象,则返回 true”,用于所有“除字符串之外的引用类型”。因此,它的等价物(只要它没有重载)。

我个人也喜欢使用第二种语法,并且发现它更易于维护以进行空值检查。我还认为任何重载的operator== 也应该提供对null 的适当检查,如果由于某种原因它没有(这很奇怪),那么该决定背后可能有一个特定的理由这会导致您想要使用重载,而不是 ReferenceEquals

【讨论】:

    【解决方案2】:

    使用 c# 7,您可以使用:

    if ( !(myObject is null) )
    

    相当于

    if (!ReferenceEquals(myObject, null))
    

    【讨论】:

    • 你确定吗? The C# reference 表示“表达式的值由调用静态 Object.Equals(expr, constant) 方法确定。” if ( !(myObject is null) ) 转换为 if (myObject != null) in SharpLab。相关:stackoverflow.com/a/42814441
    • @Kevinoid - 现在,那个 ref 说 “对于引用类型,它使用 (object)x == null。”。我认为这意味着它显式调用了object 实现,这确实相当于ReferenceEquals。 (就此而言,这也是Object.equals(..) 的意思——不使用任何覆盖。)
    【解决方案3】:

    好吧,如果有人要覆盖 == 或 != 运算符,他们可以让他们为所欲为。甚至可以让它做一些真正有意义的事情,比如return true;return false;。此外,如果有一个重载的运算符,它很有可能不会像ReferenceEquals 那样执行(不能保证,它可能还不够重要,但仍然如此)。

    话虽如此,因为任何重载运算符的任何合理实现都不太可能成为问题。我个人不使用ReferenceEquals,除非我有令人信服的理由不对该类型或特定实例使用== 运算符。

    【讨论】:

    • 如果重载的== 操作符实现不佳/不正确,那么那就是应该修复的错误。使用ReferenceEquals() 解决它不是正确的方法。
    • @MichaelBurr 您假设您可以控制类型,因此有能力修复它。情况并非总是如此。
    • @MichaelBurr 重载的== 肯定应该被修复,但我认为使用ReferenceEquals() 是可靠的防御性编程,而不是一种解决方法。
    • @ToolmakerSteve 该问题明确指出,它是关于将其与空文字进行比较。我从来没有说过ReferenceEquals 没有位置,因为== 应该具有引用语义,只是在合理的实现中值实际上为null 时才应该等于null。
    • 好的。我读到“我个人不使用 ReferenceEquals,除非我有令人信服的理由不对该类型或特定实例使用 == 运算符。” 作为对ReferenceEquals 的更一般的避免,并希望澄清你在说什么。
    【解决方案4】:

    就空值检查而言,两者应始终返回相同的结果。如果非空引用曾经等于空(即使在使用空对象模式时),无论是使用 ReferenceEquals 还是 == 运算符,这都是一件 非常 坏事。所以,在那种情况下,我会使用 ==/!=。

    我想说,如果 == 运算符重载,使用 ReferenceEquals 可能稍微快一些。重载 == 应该做的第一件事是查看两个变量是否指向同一个对象,因此在重载运算符的情况下,您最终会在调用堆栈上获得额外的帧。使用 ReferenceEquals 还可以保证这是执行的检查。

    我通常也会在几乎任何其他情况下使用 ==/!=。整个想法是运算符定义“平等”;这并不总是引用的(事实上,大多数复合对象应该在结构上比较是否相等;如果它们的成员相等,它们就相等)。从理论上讲,对象知道如何最好地将自己与另一个对象进行比较,例如相等、相对顺序等,因此与其硬编码一个非常具体且可能不正确的逻辑,您应该使用语言的面向对象特性来让对象告诉你它是否等于其他任何东西。

    【讨论】:

    • 实际上,重载通常做的第一件事就是“尝试强制转换”other 到同一个类。如果结果为null,则已知为not equal。不过你的观点是有效的:无论做什么检查都需要一些嵌套调用。
    • “整个想法是运算符 [你不是说“类”吗?] 定义了“平等””。确切地。所以如果你想知道两个变量是否引用同一个对象,你不应该使用==,你应该使用ReferenceEquals,因为这就是那个电话。不过,我同意== null 是一个特例,可以正常工作。
    【解决方案5】:

    VB.NET 标签可能不应该包含在这个问题中,因为它没有另外提及,但为了完整起见,Is 等同于Object.ReferenceEquals,因此可以始终使用而不是那个调用。

    【讨论】:

      【解决方案6】:

      所以,即使它已有一百万年的历史,我也想插话。

      假设我们要编写一个检查 null 的扩展方法。我们可以这样做:

      public static bool IsNull<T>(this T value) where T : class, new()
      { 
         return value == null;
      }
      

      但是这段代码很无聊。

      原因是 T 必须是一个类。为什么?因为我们无法将值类型与 null 进行比较。这使得编写体面的通用代码变得非常痛苦。

      或者,您可以将相同的方法附加到对象上,而不必关心该对象是什么。

      public static bool IsNull(this object value)
      {
         return object.ReferenceEquals(value, null);
      }
      

      这开启了其他可能性,例如为 Linq 编写容错 monad,或者你有什么 - 没有实际限制你的通用代码。

      【讨论】:

      • @ToolmakerSteve 我相信你是不正确的。示例上下文中的“this”关键字定义了现有类的扩展方法,在本例中是所有东西都继承自的“对象”类。上述方法将起作用并且不会引发异常。即,string str = null; if (str.IsNull()) // do something 将起作用。编译器会知道字符串类没有“IsNull”函数,但也会知道您已经定义了扩展方法。于是编译器调用 IsNull(str),然后调用对象类的静态 ReferenceEquals 方法。
      • @ToolmakerSteve 附加参考:docs.microsoft.com/en-us/dotnet/csharp/programming-guide/… 编辑 ps:我个人喜欢为我想要做某事并处理变量为空的情况定义扩展方法,因为即使我仍然可以调用扩展方法变量为null,然后在扩展方法中首先检查null,处理它,然后执行我想要的任何其他简单逻辑,将其简化为对原始类对象的函数调用。
      • @B.O.B. - 呸,我可以发誓我在几年前就测试过了。但是我测试的内容涉及调用this.somefunc() - 这里是value.somefunc() - inside 扩展方法 - 导致异常,而不是原始扩展调用。谢谢指正!
      【解决方案7】:

      ReferenceEquals 可能会稍微快一些。如前所述,它确保没有调用重载的 Equals 运算符。 ReferenceEquals 还确保仅执行一次比较,而不是可能执行 2 次,具体取决于重载的 Equals 运算符的实现。尽管重载的运算符很可能将 ReferenceEquals 本身用作第一条语句。

      我个人确实使用 ReferenceEquals,但只在我真正调整以挤出最后一个时钟周期的地方(可能被称为每秒数百万次的地方)。或者当我无法控制类型时,例如在泛型的情况下。 ...但同样只有在性能非常关键时。

      【讨论】:

      • 请记住,运算符不是虚拟的。重要的是编译时类型,而不是运行时类型。在泛型的情况下,除非对重载== 运算符的类型有约束,否则使用== 将始终导致使用object 重载,它不需要做任何检查,只需调用@ 987654324@。在这些情况下,它不仅在功能上是等效的,而且性能也几乎相同。这使得泛型成为使用== 而不是ReferenceEquals最安全 时间之一。
      • @Servy - 等等,什么? (object) ==不是定义为调用第一个参数的Equals方法吗?在自定义类中定义 Equals 方法的目的不是覆盖该相等测试吗?
      • @ToolmakerSteve 它不只是调用object.Equals。它将进行空检查,如果两者都是非空 ,它将使用虚拟 object.Equals 来确定结果。由于传入的一个值为 null,我们知道这不会发生在这里。
      【解决方案8】:

      真的,真的很晚才回复 - 但我在阅读 EFCore 库时点击了这篇文章并遇到了这种方法......

      Microsoft.EntityFrameworkCore.Utilities 中有一个 Check 类,它使用此逻辑进行空值检查......

              internal static class Check
          {
              [ContractAnnotation("value:null => halt")]
              public static T NotNull<T>([NoEnumeration] T value, [InvokerParameterName] [NotNull] string parameterName)
              {
      #pragma warning disable IDE0041 // Use 'is null' check
                  if (ReferenceEquals(value, null))
      #pragma warning restore IDE0041 // Use 'is null' check
                  {
                      NotEmpty(parameterName, nameof(parameterName));
      
                      throw new ArgumentNullException(parameterName);
                  }
      
                  return value;
              }
      

      【讨论】:

        【解决方案9】:

        正如其他答案中所解释的,这两个调用的语义略有不同。

        如果您想使用 ReferenceEquals 的语义但使用简化的语法,对于引用类型,您还可以使用:

        if (myObject is object)
        

        【讨论】:

          【解决方案10】:

          使用 C# 9,您可以使用 if (myObject is not null)The reference 声明以下安全保证:

          当您将表达式与 null 匹配时,编译器保证不会调用用户重载的 == 或 != 运算符。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-10-08
            • 1970-01-01
            • 1970-01-01
            • 2019-11-05
            • 2023-03-27
            • 1970-01-01
            • 2010-09-14
            相关资源
            最近更新 更多