【问题标题】:How do I check for nulls in an '==' operator overload without infinite recursion?如何在没有无限递归的情况下检查“==”运算符重载中的空值?
【发布时间】:2010-09-09 13:52:24
【问题描述】:

以下会导致==运算符重载方法无限递归

    Foo foo1 = null;
    Foo foo2 = new Foo();
    Assert.IsFalse(foo1 == foo2);

    public static bool operator ==(Foo foo1, Foo foo2) {
        if (foo1 == null) return foo2 == null;
        return foo1.Equals(foo2);
    }

如何检查空值?

【问题讨论】:

    标签: c# .net operator-overloading


    【解决方案1】:

    使用ReferenceEquals:

    Foo foo1 = null;
    Foo foo2 = new Foo();
    Assert.IsFalse(foo1 == foo2);
    
    public static bool operator ==(Foo foo1, Foo foo2) {
        if (object.ReferenceEquals(null, foo1))
            return object.ReferenceEquals(null, foo2);
        return foo1.Equals(foo2);
    }
    

    【讨论】:

    • 此解决方案不适用于Assert.IsFalse(foo2 == foo1);
    • 那么foo1.Equals(foo2) 是什么意思,例如,如果我想要foo1 == foo2,只有foo1.x == foo2.x && foo1.y == foo2.y?这个回答不是忽略了foo1 != null而是foo2 == null的情况吗?
    • 注意:同样的解决方案,语法更简单:if (foo1 is null) return foo2 is null;
    【解决方案2】:

    在重载方法中强制转换为对象:

    public static bool operator ==(Foo foo1, Foo foo2) {
        if ((object) foo1 == null) return (object) foo2 == null;
        return foo1.Equals(foo2);
    }
    

    【讨论】:

    • 没错。 (object)foo1 == nullfoo1 == (object)null 都将转到内置重载 ==(object, object) 而不是用户定义的重载 ==(Foo, Foo)。这就像方法上的重载决议。
    • 对于未来的访问者 - 接受的答案是一个函数,它执行对象的 ==。这与公认的答案基本相同,但有一个缺点:它需要演员表。因此,公认的答案是优越的。
    • @Mafii 演员表纯粹是编译时操作。由于编译器知道强制转换不会失败,因此它不需要在运行时检查任何内容。方法之间的差异完全是美学上的。
    【解决方案3】:

    使用@987654321@。来自MSDN forums

    public static bool operator ==(Foo foo1, Foo foo2) {
        if (ReferenceEquals(foo1, null)) return ReferenceEquals(foo2, null);
        if (ReferenceEquals(foo2, null)) return false;
        return foo1.field1 == foo2.field2;
    }
    

    【讨论】:

      【解决方案4】:

      如果您使用的是 C# 7 或更高版本,则可以使用空常量模式匹配:

      public static bool operator==(Foo foo1, Foo foo2)
      {
          if (foo1 is null)
              return foo2 is null;
          return foo1.Equals(foo2);
      }
      

      这为您提供了比调用 object.ReferenceEquals(foo1, null) 稍微简洁的代码

      【讨论】:

      • public static bool operator==( Foo foo1, Foo foo2 ) => foo1?.Equals( foo2 ) ?? foo2 is null;
      【解决方案5】:

      试试Object.ReferenceEquals(foo1, null)

      无论如何,我不建议重载==operator;它应该用于比较引用,并使用Equals 进行“语义”比较。

      【讨论】:

        【解决方案6】:

        在这种情况下,实际上有一种更简单的方法来检查null

        if (foo is null)
        

        就是这样!

        此功能是在 C# 7 中引入的

        【讨论】:

          【解决方案7】:

          如果我重写了 bool Equals(object obj) 并且我希望运算符 ==Foo.Equals(object obj) 返回相同的值,我通常会像这样实现 != 运算符:

          public static bool operator ==(Foo foo1, Foo foo2) {
            return object.Equals(foo1, foo2);
          }
          public static bool operator !=(Foo foo1, Foo foo2) {
            return !object.Equals(foo1, foo2);
          }
          

          运营商== 将在为我完成所有空值检查后最终调用foo1.Equals(foo2),我已覆盖该foo1.Equals(foo2) 以执行实际检查两者是否相等。

          【讨论】:

          • 这个感觉很合适;查看Object.Equals(Object, Object)Object.ReferenceEquals(Object, Object) 的实现,很明显Object.Equals(Object, Object) 所做的一切都是开箱即用的其他答案中所建议的。为什么不使用它?
          • @tne 因为如果你想要的只是默认行为,那么重载 == 运算符是没有意义的。只有在需要实现自定义比较逻辑时才应该重载,即不仅仅是引用相等检查。
          • @Dan 我相信你误解了我的话;在已经确定重载== 是可取的(问题暗示它)的情况下,我只是通过建议Object.Equals(Object, Object) 使其他技巧(例如使用ReferenceEquals 或不需要显式强制转换)来支持这个答案(因此“为什么不使用它?”,“它”是Equals(Object, Object))。即使不相关,您的观点也是正确的,我会更进一步:仅重载== 用于我们可以归类为“值对象”的对象。
          • @tne 主要区别在于Object.Equals(Object, Object) 依次调用Object.Equals(Object),这是Foo 可能覆盖的虚方法。您在相等性检查中引入了虚拟调用这一事实可能会影响编译器优化(例如内联)这些调用的能力。对于大多数用途而言,这可能可以忽略不计,但在某些情况下,相等运算符中的小成本可能意味着循环或排序数据结构的巨大成本。
          • @tne 有关优化虚方法调用的复杂性的更多信息,请参阅stackoverflow.com/questions/530799/…
          【解决方案8】:

          我的做法是做

          (object)item == null
          

          我依赖object 自己的等式运算符,它不会出错。或者自定义扩展方法(和重载):

          public static bool IsNull<T>(this T obj) where T : class
          {
              return (object)obj == null;
          }
          
          public static bool IsNull<T>(this T? obj) where T : struct
          {
              return !obj.HasValue;
          }
          

          或者处理更多的情况,可能是:

          public static bool IsNull<T>(this T obj) where T : class
          {
              return (object)obj == null || obj == DBNull.Value;
          }
          

          该约束阻止 IsNull 对值类型进行处理。现在就像打电话一样甜蜜

          object obj = new object();
          Guid? guid = null; 
          bool b = obj.IsNull(); // false
          b = guid.IsNull(); // true
          2.IsNull(); // error
          

          这意味着我有一种始终如一的/不易出错的检查空值的方式。我也找到了(object)item == null is very very very slightly faster than Object.ReferenceEquals(item, null),但前提是它很重要(我目前正在做一些我必须对所有内容进行微优化的事情!)。

          要查看有关实施平等检查的完整指南,请参阅What is "Best Practice" For Comparing Two Instances of a Reference Type?

          【讨论】:

          • Nitpick:读者应该先观察它们的依赖关系,然后再使用比较 DbNull 等功能,IMO 这种不会产生与 SRP 相关的问题的情况非常罕见. 只是指出代码的味道,这很合适。
          【解决方案9】:

          The static Equals(Object, Object) method 表示两个对象objAobjB 是否相等。它还使您能够测试值为null 的对象是否相等。它比较 objAobjB 是否相等,如下所示:

          • 判断两个对象是否代表同一个对象引用。如果是,则该方法返回true。该测试相当于调用ReferenceEquals 方法。另外,如果objAobjB都是null,则该方法返回true
          • 它确定objAobjB 是否为null。如果是,则返回false。 如果两个对象不代表同一个对象引用,null 也不代表,它调用objA.Equals(objB) 并返回结果。这意味着如果objA 覆盖Object.Equals(Object) 方法,则会调用此覆盖。

          .

          public static bool operator ==(Foo objA, Foo objB) {
              return Object.Equals(objA, objB);
          }
          

          【讨论】:

            【解决方案10】:

            回复更多overriding operator how to compare to null,将此处重定向为重复。

            在这样做以支持值对象的情况下,我发现新的符号很方便,并且希望确保只有一个地方进行比较。还利用 Object.Equals(A, B) 简化了 null 检查。

            这将重载 ==、!=、Equals 和 GetHashCode

                public static bool operator !=(ValueObject self, ValueObject other) => !Equals(self, other);
                public static bool operator ==(ValueObject self, ValueObject other) => Equals(self, other);
                public override bool Equals(object other) => Equals(other as ValueObject );
                public bool Equals(ValueObject other) {
                    return !(other is null) && 
                           // Value comparisons
                           _value == other._value;
                }
                public override int GetHashCode() => _value.GetHashCode();
            

            对于更复杂的对象,在 Equals 和更丰富的 GetHashCode 中添加额外的比较。

            【讨论】:

              【解决方案11】:

              对于现代且简洁的语法:

              public static bool operator ==(Foo x, Foo y)
              {
                  return x is null ? y is null : x.Equals(y);
              }
              
              public static bool operator !=(Foo x, Foo y)
              {
                  return x is null ? !(y is null) : !x.Equals(y);
              }
              

              【讨论】:

                【解决方案12】:

                运算符 == 重载的一个常见错误是使用 (a == b)(a ==null)(b == null) 来检查引用是否相等。这反而 导致调用重载运算符 ==,从而导致 infinite loop。使用ReferenceEquals 或将类型转换为 Object,以避免 循环。

                看看这个

                // If both are null, or both are same instance, return true.
                if (System.Object.ReferenceEquals(a, b))// using ReferenceEquals
                {
                    return true;
                }
                
                // If one is null, but not both, return false.
                if (((object)a == null) || ((object)b == null))// using casting the type to Object
                {
                    return false;
                }
                

                参考Guidelines for Overloading Equals() and Operator ==

                【讨论】:

                • 所有这些信息已经有多个答案。我们不需要相同答案的第 7 份副本。
                【解决方案13】:

                您可以尝试使用对象属性并捕获生成的 NullReferenceException。如果您尝试的属性是从 Object 继承或覆盖的,那么这适用于任何类。

                public static bool operator ==(Foo foo1, Foo foo2)
                {
                    //  check if the left parameter is null
                    bool LeftNull = false;
                    try { Type temp = a_left.GetType(); }
                    catch { LeftNull = true; }
                
                    //  check if the right parameter is null
                    bool RightNull = false;
                    try { Type temp = a_right.GetType(); }
                    catch { RightNull = true; }
                
                    //  null checking results
                    if (LeftNull && RightNull) return true;
                    else if (LeftNull || RightNull) return false;
                    else return foo1.field1 == foo2.field2;
                }
                

                【讨论】:

                • 如果你有很多空对象,那么异常处理可能是一个很大的开销。
                • 哈哈,我同意这不是最好的方法。发布此方法后,我立即修改了我当前的项目以使用 ReferenceEquals 代替。然而,尽管它不是最理想的,但它确实有效,因此是该问题的有效答案。
                猜你喜欢
                • 2011-08-30
                • 1970-01-01
                • 2010-09-27
                • 1970-01-01
                • 2021-06-09
                • 2018-09-12
                • 1970-01-01
                • 2020-05-30
                相关资源
                最近更新 更多