关于c#:如何在没有无限递归的情况下检查’==’运算符重载中的null?

关于c#:如何在没有无限递归的情况下检查’==’运算符重载中的null?

How do I check for nulls in an '==' operator overload without infinite recursion?

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

1
2
3
4
5
6
7
8
    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);
    }

如何检查空值?


使用ReferenceEquals

1
2
3
4
5
6
7
8
9
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
2
3
4
public static bool operator ==(Foo foo1, Foo foo2) {
    if ((object) foo1 == null) return (object) foo2 == null;
    return foo1.Equals(foo2);
}


使用ReferenceEquals。从MSDN论坛:

1
2
3
4
5
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;
}

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

1
2
3
4
5
6
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.ReferenceEquals(foo1, null)

无论如何,我不建议重载==操作符。它应该用于比较引用,并使用Equals进行"语义"比较。


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

1
2
3
4
5
6
public static bool operator==(Foo foo1, Foo foo2)
{
    if (foo1 is null)
        return foo2 is null;
    return foo1.Equals(foo2);
}

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


我的方法是

1
(object)item == null

我依靠的是object自己的相等运算符,不会出错。或自定义扩展方法(以及重载):

1
2
3
4
5
6
7
8
9
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;
}

或处理更多案件,可能是:

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

该约束可防止在值类型上使用IsNull。现在就像打电话一样甜蜜

1
2
3
4
5
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)的速度非常非常快,但是只有在这很重要的时候(我正在研究必须对所有事物进行微优化的事物!)。

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


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

1
if (foo is null)

而已!

此功能是C#7中引入的


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

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

这将重载==,!=,等于和GetHashCode

1
2
3
4
5
6
7
8
9
    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中添加其他比较。


静态Equals(Object,?Object)方法指示两个对象objAobjB是否相等。它还使您能够测试值为null的对象是否相等。它将objAobjB的相等性进行比较,如下所示:

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

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

A common error in overloads of operator == is to use (a == b), (a ==null), or (b == null) to check for reference equality. This instead
results in a call to the overloaded operator ==, causing an infinite loop. Use ReferenceEquals or cast the type to Object, to avoid the
loop.

看看这个

1
2
3
4
5
6
7
8
9
10
11
// 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()和运算符的准则==


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}


推荐阅读