如何在没有无限递归的情况下检查'=='运算符重载中的空值?


113

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

    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);
    }

如何检查空值?

Answers:


138

用途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);
FIL

foo1.Equals(foo2)举例来说,foo1 == foo2如果我只想要,这意味着什么foo1.x == foo2.x && foo1.y == foo2.y?这不是应答忽略的情况下foo1 != null,但foo2 == null
丹尼尔(Daniel)

注意:相同的解决方案,语法更简单:if (foo1 is null) return foo2 is null;
Rem

20

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

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

1
究竟。双方(object)foo1 == nullfoo1 == (object)null会进入内置的过载==(object, object),而不是用户定义的过载==(Foo, Foo)。就像方法上的重载解析一样。
杰普·斯蒂格·尼尔森

2
对于未来的访问者-接受的答案是一个函数,该函数执行object的==。这基本上与接受的答案相同,但有一个缺点:它需要强制转换。因此,获得的答案是优越的。
马菲

1
@Mafii强制转换纯粹是编译时操作。由于编译器知道强制转换不会失败,因此它无需在运行时检查任何内容。方法之间的差异是完全美学的。
Servy

8

使用ReferenceEquals。从MSDN论坛

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

尝试 Object.ReferenceEquals(foo1, null)

无论如何,我不建议重载==运算符。它应该用于比较参考,并Equals用于“语义”比较。


4

如果我已经重写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)我已被覆盖以进行实际检查(如果两者相等)。


感觉很合适;纵观与Object.Equals(Object, Object)并排的实现,Object.ReferenceEquals(Object, Object)很明显,Object.Equals(Object, Object)其他所有答案都按照开箱即用的方式完成了所有工作。为什么不使用它?
TNE

@tne因为==只要您想要的只是默认行为,操作符就没有意义。仅在需要实现自定义比较逻辑(即,除了引用相等性检查以外的其他内容)时才应重载。
丹·贝查德

@丹我确信你误解了我的话;在已经确定需要重载的==情况下(问题暗示了这一点),我只是通过暗示不需要Object.Equals(Object, Object)其他技巧(例如使用ReferenceEquals或显式强制转换)来支持该答案(因此“为什么不使用它?”,“它”是Equals(Object, Object))。即使不相关,您的观点也是正确的,并且我会进一步讲:只有==对于超载的对象,我们才能将其归类为“值对象”。
TNE

@tne主要区别在于Object.Equals(Object, Object)依次调用Object.Equals(Object),这是Foo可能会覆盖的虚拟方法。您已经在相等性检查中引入了虚拟调用的事实可能会影响编译器优化(例如,内联)这些调用的能力。在大多数情况下,这可能是微不足道的,但是在某些情况下,等于运算符的少量开销可能意味着循环或排序数据结构的巨大开销。
丹·贝查德

@tne有关优化虚拟方法调用的复杂性的更多信息,请参阅stackoverflow.com/questions/530799/…
丹·贝查德

3

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

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

这使您的代码比一个调用对象稍微整洁.ReferenceEquals(foo1,null)


2
public static bool operator==( Foo foo1, Foo foo2 ) => foo1?.Equals( foo2 ) ?? foo2 is null;
DankoDurbić18年


1

我的方法是

(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

这意味着我有一种始终如一/不易出错的样式来检查null。我还发现(object)item == null它的速度比非常非常非常快Object.ReferenceEquals(item, null),但前提是这很重要(我目前正在研究必须对所有事物进行微优化的事物!)。

若要查看有关实现相等性检查的完整指南,请参阅什么是比较引用类型的两个实例的“最佳实践”?


Nitpick:读者在开始使用compare DbNull,IMO之类的功能之前,应先注意其依赖性,这种情况很少产生与SRP相关的问题。虽然只是指出了代码的味道,但它很合适。
TNE

0

静态Equals(Object, Object)方法指示objA和的两个对象是否objB相等。它还使您能够测试其值是否null相等的对象。它比较objAobjB平等,如下所示:

  • 它确定两个对象是否代表相同的对象引用。如果这样做,则该方法返回true。此测试等效于调用该ReferenceEquals方法。另外,如果objAobjB均为null,则该方法返回true
  • 它确定是否任一objAobjBnull。如果是这样,则返回false。如果两个对象不代表相同的对象引用,也不是null,则它调用objA.Equals(objB)并返回结果。这意味着如果objA重写该Object.Equals(Object)方法,则将调用此重写。

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

0

向主要操作员回复更多有关如何与null进行比较的信息,此处将null重定向为重复项。

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

这将重载==,!=,等于和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中添加其他比较。


0

对于现代和简洁的语法:

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);
}

-3

在操作者的重载一个常见的错误==是使用(a == b)(a ==null)(b == null)以检查引用相等。相反导致对重载运算符==的调用,从而导致infinite loop。使用ReferenceEquals类型或将其强制转换为对象,以避免循环。

看看这个

// 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;
}

参考 重载Equals()和运算符的准则==


1
所有这些信息已经有多个答案。我们不需要相同答案的第7个副本。
Servy '16

-5

您可以尝试使用对象属性并捕获生成的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;
}

如果您有许多空对象,那么异常处理可能会带来很大的开销。
Kasprzol

2
哈哈,我同意这不是最好的方法。发布此方法后,我立即修改了当前项目以使用ReferenceEquals。但是,尽管它不是次优的,但它确实有效,因此是对该问题的有效答案。
Digital Gabeg
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.