编写equals运算符时处理null值的最佳方法是什么?

7
可能重复:
如何在“==”运算符重载中检查空值,而不会无限递归?

当我为对象重载==运算符时,通常会编写类似于以下内容的代码:

    public static bool operator ==(MyObject uq1, MyObject uq2) {
        if (((object)uq1 == null) || ((object)uq2 == null)) return false;
        return uq1.Field1 == uq2.Field1 && uq1.Field2 == uq2.Field2;
    }

如果您不将其向下转换为对象,该函数将递归自身,但我不禁想知道是否有更好的方法?


1
还可以查看此链接以获取适当的全面实现:https://dev59.com/XXVD5IYBdhLWcg3wDXJ3 - nawfal
7个回答

7
正如微软所说, 在重载的 operator == 中,常见的错误是使用 (a == b)、(a == null) 或 (b == null) 来检查引用相等性。这将导致调用重载的 operator ==,从而导致无限循环。使用 ReferenceEquals 或将类型转换为 Object,以避免循环。 因此,使用 ReferenceEquals(a, null) || ReferenceEquals(b, null) 是一种可能性,但将其转换为对象也同样好(实际上是等效的,我相信)。 所以是的,似乎应该有更好的方法,但你使用的方法是推荐的方法。 然而,正如已经指出的那样,当重载 == 时,你真的应该同时重载 Equals。由于 LINQ 提供程序使用不同的语言编写,并在运行时进行表达式解析,因此即使你自己拥有所有代码,也不知道何时会受到未执行此操作的影响。

是的,这是一个常见的问题,但解决方法是重写Equals函数并使运算符重载调用Equals。ReferenceEquals解决了不同的问题,并提供了完全不同类型的相等性。 - Scott Dorman
2
我认为你误解了上面的ReferenceEquals调用。我只是在测试与NULL的相等性,并理解除了进行实际的相等性检查之外,不调用referenceequals来测试参数的相等性。 - Philip Rieck

2

ReferenceEquals(object obj1, object obj2)


这似乎根本没有解决问题 - ReferenceEquals 不会告诉你一个是 null 而另一个不是,例如。 - Philip Rieck
为什么人们投票支持这个,从我的示例中可以清楚地看出ReferenceEquals不是我要找的东西。 - George Mauer
呵呵,+4票和-4票 :) - ripper234
@GeorgeMauer 给这个答案点个赞,因为这正是甚至 MSDN 推荐的做法 http://msdn.microsoft.com/en-us/library/ms173147(VS.80).aspx - nawfal

2

@neouser99: 这是正确的解决方案,但是遗漏的部分是当覆盖相等运算符(即操作符==)时,您还应该覆盖Equals函数并简单地使操作符调用该函数。并非所有的.NET语言都支持操作符重载,因此需要覆盖Equals函数。


1
真的吗?那很有趣,我一直都是用另一种方式做的。 - George Mauer
如果您这样做,请确保“==”运算符检查null。null == null 总是成立。 - Joe Hildebrand

0

使用Resharper创建您的Equals & GetHashCode方法。它为此目的创建了最全面的代码。

更新 我没有刻意发布它 - 我更希望人们使用Resharper的功能而不是复制粘贴,因为代码会从一个类变为另一个类。至于不使用Resharper开发C# - 我不知道你是怎么过日子的,老兄。

无论如何,这是一个简单类的代码(由较旧版本的Resharper 3.0生成 - 我在工作中有4.0版本,我现在不记得它是否创建了相同的代码)

public class Foo : IEquatable<Foo>
{
    public static bool operator !=(Foo foo1, Foo foo2)
    {
        return !Equals(foo1, foo2);
    }

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

    public bool Equals(Foo foo)
    {
        if (foo == null) return false;
        return y == foo.y && x == foo.x;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(this, obj)) return true;
        return Equals(obj as Foo);
    }

    public override int GetHashCode()
    {
        return y + 29*x;
    }

    private int y;
    private int x;
}

1
你不会有意愿为那些没有400美元花费的人发布它,对吧? - George Mauer
1
@Hamish - 我不知道,你需要提供更具体的信息。 - ripper234
相等必须是对称的。如果类D继承自类B,你永远不希望b.Equals(d)与d.Equals(b)不同。因此,Equals(object obj)也应该:A)检查obj == null,B)检查this.GetType() == obj.GetType()。这就是他说的。 - Hamish Grubijan
这个答案有很多陷阱,首先它没有回答 OP 的原始要求,即 "如果它们中的任何一个为 null,则 == 应该返回 false"。在你的情况下,null == null 返回 true。其次,在你重写的 Equals 中不需要 if (ReferenceEquals(this, obj)) return true;,因为这本质上将由你的 static Equals(obj, obj) 方法完成。也许你最好在 generic Equals 内部加速时使用它。请参阅此链接以获取正确的实现:https://dev59.com/XXVD5IYBdhLWcg3wDXJ3 - nawfal
  1. 最好使用!=作为==运算符的否定,而不是使用Equals方法。仅仅为了简洁起见。
- nawfal
显示剩余2条评论

0
if ((object)uq1 == null) 
    return ((object)uq2 == null)
else if ((object)uq2 == null)
    return false;
else
    //return normal comparison

当两者都为空时,它们被视为相等。


1
这个做的和我的例子一样,只是用了更加复杂的术语。 - George Mauer
不,它不会 @GeorgeMauer - 如果它们都是“null”,你的代码会返回false。Timothy的解决方案在这种情况下正确地返回true。 - mjwills

-1

但是为什么不创建一个对象成员函数呢?它肯定不能在 Null 引用上调用,所以你可以确保第一个参数不是 Null。

的确,你会失去二元运算符的对称性,但是...

(关于 Purfideas 的回答:如果需要作为数组的哨兵值,则 Null 可能等于 Null)

还要考虑你的 == 函数的语义:有时候你真的想选择测试

  • 身份(指向同一对象)
  • 值相等
  • 等价(例如 1.000001 等价于 .9999999)

1
很抱歉,这并没有回答问题。问题是是否有比向下转换更好的测试相等的方法。 - George Mauer

-2

遵循数据库处理:

null == <anything> is always false

这并没有解决问题,如果其中一个为null,x == y将会递归函数,就像我所说的那样。 - George Mauer

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