如何在“==”运算符重载中检查空值而不导致无限递归?该问题涉及IT技术。

124
以下内容会导致"=="运算符重载方法出现无限递归。
    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);
    }

如何检查 null 值?

13个回答

150

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

1
这个解决方案不适用于 Assert.IsFalse(foo2 == foo1); - FIL
如果我只想在 foo1.x == foo2.x && foo1.y == foo2.y 的情况下让 foo1 == foo2,那么 foo1.Equals(foo2) 是什么意思?这是否忽略了 foo1 != nullfoo2 == null 的情况? - Daniel
6
注意:相同解决方案,语法更简单:if (foo1 is null) return foo2 is null; - Rémi Gaudin

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)。这就像方法的重载解析一样。 - Jeppe Stig Nielsen
2
给未来的访客们 - 接受的答案是一个函数,它执行对象的 == 操作。这基本上与接受的答案相同,只有一个缺点:它需要一个转换。因此,接受的答案更优秀。 - Mafii
1
@Mafii 强制类型转换是纯粹的编译时操作。由于编译器知道强制类型转换不会失败,因此它在运行时不需要检查任何内容。这些方法之间的差异完全是美学上的。 - Servy

10
如果您使用的是 C# 7 或更高版本,则可以使用 null 常量模式匹配:
public static bool operator==(Foo foo1, Foo foo2)
{
    if (foo1 is null)
        return foo2 is null;
    return foo1.Equals(foo2);
}

这样做可以让您的代码略微更加整洁,比使用object.ReferenceEquals(foo1, null)更好


6
或者 public static bool operator==(Foo foo1, Foo foo2) => foo1?.Equals(foo2) ?? foo2 is null;。该代码为C#语言,定义了两个Foo对象之间的等于操作符。它使用了null合并运算符(??),如果foo1为null,则返回foo2是否为null的布尔值。否则,调用foo1的Equals方法比较foo1和foo2是否相等。最终返回比较结果的布尔值。 - Danko Durbić

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

如果我重写了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
1
因为如果你只需要默认行为,那么重载==运算符就没有意义。只有在需要实现自定义比较逻辑时,即需要进行引用相等性检查之外的操作时,才应该进行重载。 - Dan Bechard
@丹,我相信你误解了我的话; 在已经确定重载==是可取的情况下(问题意味着这一点),我只是通过建议使用Object.Equals(Object, Object)来支持这个答案,以此来避免使用ReferenceEquals或显式转换等其他技巧(因此,“为什么不使用它?”,“它”指的是Equals(Object, Object))。即使与此无关,你的观点也是正确的,而且我会进一步说:只有将对象归类为“值对象”时才会重载== - tne
主要区别在于Object.Equals(Object, Object)反过来调用了Object.Equals(Object),这是Foo可能重写的虚方法。你在相等性检查中引入了一个虚拟调用,这可能会影响编译器优化(例如内联)这些调用的能力。对于大多数目的来说,这可能是可以忽略不计的,但在某些情况下,相等运算符中的小成本可能意味着循环或排序数据结构的巨大成本。 - Dan Bechard
有关优化虚方法调用的细节信息,请参阅https://dev59.com/nXRB5IYBdhLWcg3wvpic。 - Dan Bechard
显示剩余4条评论

4

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

if (foo is null)

就是这样啦!

这个特性在C# 7中引入。


4
尝试使用Object.ReferenceEquals(foo1, null)
无论如何,我不建议重载==操作符; 它应该用于比较引用,并使用Equals进行“语义”比较。

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

这意味着我有一个一致/不容易出错的检查空值的风格。我还发现 (object)item == nullObject.ReferenceEquals(item, null) 稍微快一点, 但只有在需要时才会使用(我目前正在处理需要微调优化的事情!)。

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


吹毛求疵:读者在使用比较“DbNull”等功能之前应该注意他们的依赖关系,我认为这种情况下不会产生与SRP相关的问题的情况非常罕见。只是指出代码异味,它可能非常适合。 - tne

1

对于现代化和简洁的语法:

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

0

更多回复覆盖运算符如何与null比较,这里重定向为重复内容。

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

这将重载==、!=、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。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接