IEquatable接口检查null时要做什么

时间:2023-01-24 16:11:57

I have implemented the IEquatable interface in a class with the following code.

我已经使用以下代码在类中实现了IEquatable接口。

        public bool Equals(ClauseBE other)
        {
            if (this._id == other._id)
            {
                return true;
            }
            return false;
        }

        public override bool Equals(Object obj)
        {
            if (obj == null)
            {
                return base.Equals(obj);
            }

            if (!(obj is ClauseBE))
            {
                throw new InvalidCastException("The 'obj' argument is not a ClauseBE object.");
            }

            return Equals(obj as ClauseBE);
        }

        public override int GetHashCode()
        {
            return this._id.GetHashCode();
        }

        public static bool operator ==(ClauseBE a, ClauseBE b)
        {
            // cast to object so we call the overloaded Equals function which appropriately checks when b is null.
            return a.Equals(b as object);
        }

        public static bool operator !=(ClauseBE a, ClauseBE b)
        {
            // cast to object so we call the overloaded Equals function which appropriately checks when b is null.
            return !a.Equals(b as object);
        }

This code work very well for most all cases. However, the following check throws an exception in the equality operator overload method because a is null and therefore does not have a Equals method.

此代码适用于大多数情况。但是,以下检查会在等于运算符重载方法中引发异常,因为a为null,因此没有Equals方法。

if(this.Clause != null)
{

}

What is the standard way to solve this issue?

解决此问题的标准方法是什么?

EDIT

I have gone to this, but it seems pretty cumbersome. I was hoping there was a more elegant way to accomplish this.

我已经去过这个,但看起来很麻烦。我希望有一种更优雅的方式来实现这一目标。

    public static bool operator ==(ClauseBE a, ClauseBE b)
    {
        if (a as object == null && b as object == null)
        {
            return true;
        }

        if ((a as object == null && b as object != null)
            || (b as object == null && a as object != null))
        {
            return false;
        }

        // cast to object so we call the overloaded Equals function which appropriately checks when b is null.
        return a.Equals(b as object);
    }

    public static bool operator !=(ClauseBE a, ClauseBE b)
    {
        if (a as object == null && b as object == null)
        {
            return false;
        }

        if((a as object == null && b as object != null)
            || (b as object == null && a as object != null))
        {
            return true;
        }

        // cast to object so we call the overloaded Equals function which appropriately checks when b is null.
        return !a.Equals(b as object);
    }

Solution

Thanks all. I got a lot of good tips from everyone, I really appreciate it. This is what I finally settled on, it's a lot more elegant than what I had started with. All code is the same except operator overloads.

谢谢大家。我从每个人那里得到了很多好的建议,我真的很感激。这是我最终确定的,它比我开始时更优雅。除运算符重载外,所有代码都相同。

public static bool operator ==(ClauseBE a, ClauseBE b)
{
    if (ReferenceEquals(a, null) && ReferenceEquals(b, null))
    {
        return true;
    }

    if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
    {
        return false;
    }

    return a.Equals(b);
}

public static bool operator !=(ClauseBE a, ClauseBE b)
{
    return !(a == b);
}

8 个解决方案

#1


I've always found it easier to write the static operator with null handling, and have the Equals override call the overloaded operator with "this" as one of the parameters.

我总是发现使用null处理编写静态运算符更容易,并且让Equals覆盖调用重载运算符,并将“this”作为参数之一。

From Guidelines for Overloading Equals() and Operator == (C# Programming Guide)

来自重载等于()和运算符的指南==(C#编程指南)

//add this code to class ThreeDPoint as defined previously
//
public static bool operator ==(ThreeDPoint a, ThreeDPoint b)
{
    // If both are null, or both are same instance, return true.
    if (System.Object.ReferenceEquals(a, b))
    {
        return true;
    }

    // If one is null, but not both, return false.
    if (((object)a == null) || ((object)b == null))
    {
        return false;
    }

    // Return true if the fields match:
    return a.x == b.x && a.y == b.y && a.z == b.z;
}

public static bool operator !=(ThreeDPoint a, ThreeDPoint b)
{
    return !(a == b);
}

#2


This is how ReSharper creates equality operators and implements IEquatable<T>, which I trust blindly, of course ;-)

这就是ReSharper如何创建相等运算符并实现IEquatable ,我当然盲目相信;-)

public class ClauseBE : IEquatable<ClauseBE>
{
    private int _id;

    public bool Equals(ClauseBE other)
    {
        if (ReferenceEquals(null, other))
            return false;
        if (ReferenceEquals(this, other))
            return true;
        return other._id == this._id;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
            return false;
        if (ReferenceEquals(this, obj))
            return true;
        if (obj.GetType() != typeof(ClauseBE))
            return false;
        return Equals((ClauseBE)obj);
    }

    public override int GetHashCode()
    {
        return this._id.GetHashCode();
    }

    public static bool operator ==(ClauseBE left, ClauseBE right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ClauseBE left, ClauseBE right)
    {
        return !Equals(left, right);
    }
}

#3


Check for null and return false. Equals should always be false if one of the operands is null;

检查null并返回false。如果其中一个操作数为null,则Equals应始终为false;

#4


I think this is a bit less cumbersome than casting to Object before checking for null:

我认为这比在检查null之前转换为Object要麻烦一点:

ReferenceEquals(a, null)

#5


Other answers give good solutions to the general problem.

其他答案为一般问题提供了很好的解决方案。

However, your own code can be simplified into a relatively simple solution ...

但是,您自己的代码可以简化为一个相对简单的解决方案......

Firstly, at the start of your == operator you have this:

首先,在您的==运算符的开头,您有:

    // First test
    if (a as object == null && b as object == null)
    {
        return true;
    }

This qualifies as "working too hard".

这有资格“工作太辛苦”。

If ClauseBE is a reference type, then you only need to compare with null - the "as object" is redundant; equally, if ClauseBE is a value type, then it can never be null.

如果ClauseBE是一个引用类型,那么你只需要与null进行比较 - “as object”是多余的;同样,如果ClauseBE是一个值类型,那么它永远不能为null。

Assuming that ClauseBE is a reference type (the most likely case), then you can simplify to this - note that we use Object.Equals() to avoid infinite recursion and a stack blowout.

假设ClauseBE是一个引用类型(最可能的情况),那么你可以简化到这一点 - 注意我们使用Object.Equals()来避免无限递归和堆栈井喷。

    // First test
    if (Object.Equals(a, null) && Object.Equals(b, null))
    {
        return true;
    }

One useful shortcut is to use Object.ReferenceEquals() - which handles nulls for you.

一个有用的快捷方式是使用Object.ReferenceEquals() - 它为您处理空值。

So you could write this instead:

所以你可以这样写:

    // First test
    if (Object.ReferenceEquals(a, b))
    {
        return true;
    }

with the bonus that this also handles the case where a and b are the same exact object.

有奖金,这也处理a和b是同一个确切对象的情况。

Once you get past the Object.ReferenceEquals() test, you know that a and b are different.

一旦超过了Object.ReferenceEquals()测试,就会知道a和b是不同的。

So your next test:

所以你的下一个测试:

    // Second test
    if ((a as object == null && b as object != null)
        || (b as object == null && a as object != null))
    {
        return false;
    }

can be simplified - since you know that if a is null, b cannot be null, and so on.

可以简化 - 因为你知道如果a为null,则b不能为null,依此类推。

    // Second test
    if (Object.Equals(a, null) || Object.Equals(b, null))
    {
        return false;
    }

If this test fails, then you know that a and b are different, and that neither is null. A good time to call your overridden Equals().

如果此测试失败,那么您知道a和b不同,并且两者都不为null。是调用被覆盖的Equals()的好时机。

    // Use the implementation of Equals() for the rest
    return a.Equals(b as object);

#6


public class Foo : IEquatable<Foo>
{
    public Int32 Id { get; set; }

    public override Int32 GetHashCode()
    {
        return this.Id.GetHashCode();
    }

    public override Boolean Equals(Object obj)
    {
        return !Object.ReferenceEquals(obj as Foo, null)
            && (this.Id == ((Foo)obj).Id);

        // Alternative casting to Object to use == operator.
        return ((Object)(obj as Foo) != null) && (this.Id == ((Foo)obj).Id);
    }

    public static Boolean operator ==(Foo a, Foo b)
    {
        return Object.Equals(a, b);
    }

    public static Boolean operator !=(Foo a, Foo b)
    {
        return !Object.Equals(a, b);
    }

    public Boolean Equals(Foo other)
    {
        return Object.Equals(this, other);
    }
}

#7


I have used the following approach and it seemed to work well for me. Infact, Resharper suggests this approach.

我使用了以下方法,它似乎对我有用。事实上,Resharper建议采用这种方法。

public bool Equals(Foo pFoo)
{
        if (pFoo == null)
            return false;
        return (pFoo.Id == Id);
}

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

        return Equals(obj as Foo);
}

#8


I prefer to perform all the comparison logic in the Equals(T) method, and leave the "if this or that is null, else ..." in operator overloads to the framework.

我更喜欢在Equals(T)方法中执行所有比较逻辑,并在运算符重载到框架中留下“if this或that null,else ......”。

The only tricky thing about overriding operator overloads is that you can no longer use those operators in your Equals implementation, for example to compare with null. Instead, object.ReferenceEquals can be used to achieve the same effect.

覆盖运算符重载的唯一棘手的问题是,您不能再在Equals实现中使用这些运算符,例如与null进行比较。相反,object.ReferenceEquals可用于实现相同的效果。

Following the TwoDPoint example in the MSDN Guidelines for Overriding Equals() and Operator == article, this is the pattern I generate when implementing value equality for types:

遵循MSDN指南中的OverDPiding Equals()和Operator ==文章中的TwoDPoint示例,这是我在为类型实现值相等时生成的模式:

public override bool Equals( object obj ) {
  // Note: For value types, would use:
  // return obj is TwoDPoint && this.Equals( (TwoDPoint)obj );
  return this.Equals( obj as TwoDPoint );
}

public bool Equals( TwoDPoint other ) {
  // Note: null check not needed for value types.
  return !object.ReferenceEquals( other, null )
      && EqualityComparer<int>.Default.Equals( this.X, other.X )
      && EqualityComparer<int>.Default.Equals( this.Y, other.Y );
}

public static bool operator ==( TwoDPoint left, TwoDPoint right ) {
  // System.Collections.Generic.EqualityComparer<T> will perform the null checks 
  //  on the operands, and will call the Equals overload if necessary.
  return EqualityComparer<TwoDPoint>.Default.Equals( left, right );
}

public static bool operator !=( TwoDPoint left, TwoDPoint right ) {
  return !EqualityComparer<TwoDPoint>.Default.Equals( left, right );
}

The form above is the safest implementation, as it simply forwards the field equality checks to the framework and requires no knowledge of whether the fields overload the equality operators. It is perfectly fine to simplify this where you know the overload exists:

上面的表单是最安全的实现,因为它只是将字段相等性检查转发到框架,并且不需要知道字段是否重载相等运算符。在您知道存在过载的情况下简化此操作是完全可以的:

public bool Equals( TwoDPoint other ) {
  return !object.ReferenceEquals( other, null )
      && this.X == other.X
      && this.Y == other.Y;
}

You can also replace the EqualityComparer<T> calls in the operator overloads with calls to the static object.Equals method when comparing reference types, or when boxing value types does not matter:

您还可以在运算符重载时替换EqualityComparer 调用,并在比较引用类型时调用静态object.Equals方法,或者在装箱值类型无关紧要时:

public static bool operator ==( TwoDPoint left, TwoDPoint right ) {
  return object.Equals( left, right );
}

public static bool operator !=( TwoDPoint left, TwoDPoint right ) {
  return !object.Equals( left, right );
}

See also What is the best algorithm for an overridden GetHashCode? for implementing GetHashCode.

另请参阅重写的GetHashCode的最佳算法是什么?用于实现GetHashCode。

#1


I've always found it easier to write the static operator with null handling, and have the Equals override call the overloaded operator with "this" as one of the parameters.

我总是发现使用null处理编写静态运算符更容易,并且让Equals覆盖调用重载运算符,并将“this”作为参数之一。

From Guidelines for Overloading Equals() and Operator == (C# Programming Guide)

来自重载等于()和运算符的指南==(C#编程指南)

//add this code to class ThreeDPoint as defined previously
//
public static bool operator ==(ThreeDPoint a, ThreeDPoint b)
{
    // If both are null, or both are same instance, return true.
    if (System.Object.ReferenceEquals(a, b))
    {
        return true;
    }

    // If one is null, but not both, return false.
    if (((object)a == null) || ((object)b == null))
    {
        return false;
    }

    // Return true if the fields match:
    return a.x == b.x && a.y == b.y && a.z == b.z;
}

public static bool operator !=(ThreeDPoint a, ThreeDPoint b)
{
    return !(a == b);
}

#2


This is how ReSharper creates equality operators and implements IEquatable<T>, which I trust blindly, of course ;-)

这就是ReSharper如何创建相等运算符并实现IEquatable ,我当然盲目相信;-)

public class ClauseBE : IEquatable<ClauseBE>
{
    private int _id;

    public bool Equals(ClauseBE other)
    {
        if (ReferenceEquals(null, other))
            return false;
        if (ReferenceEquals(this, other))
            return true;
        return other._id == this._id;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
            return false;
        if (ReferenceEquals(this, obj))
            return true;
        if (obj.GetType() != typeof(ClauseBE))
            return false;
        return Equals((ClauseBE)obj);
    }

    public override int GetHashCode()
    {
        return this._id.GetHashCode();
    }

    public static bool operator ==(ClauseBE left, ClauseBE right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ClauseBE left, ClauseBE right)
    {
        return !Equals(left, right);
    }
}

#3


Check for null and return false. Equals should always be false if one of the operands is null;

检查null并返回false。如果其中一个操作数为null,则Equals应始终为false;

#4


I think this is a bit less cumbersome than casting to Object before checking for null:

我认为这比在检查null之前转换为Object要麻烦一点:

ReferenceEquals(a, null)

#5


Other answers give good solutions to the general problem.

其他答案为一般问题提供了很好的解决方案。

However, your own code can be simplified into a relatively simple solution ...

但是,您自己的代码可以简化为一个相对简单的解决方案......

Firstly, at the start of your == operator you have this:

首先,在您的==运算符的开头,您有:

    // First test
    if (a as object == null && b as object == null)
    {
        return true;
    }

This qualifies as "working too hard".

这有资格“工作太辛苦”。

If ClauseBE is a reference type, then you only need to compare with null - the "as object" is redundant; equally, if ClauseBE is a value type, then it can never be null.

如果ClauseBE是一个引用类型,那么你只需要与null进行比较 - “as object”是多余的;同样,如果ClauseBE是一个值类型,那么它永远不能为null。

Assuming that ClauseBE is a reference type (the most likely case), then you can simplify to this - note that we use Object.Equals() to avoid infinite recursion and a stack blowout.

假设ClauseBE是一个引用类型(最可能的情况),那么你可以简化到这一点 - 注意我们使用Object.Equals()来避免无限递归和堆栈井喷。

    // First test
    if (Object.Equals(a, null) && Object.Equals(b, null))
    {
        return true;
    }

One useful shortcut is to use Object.ReferenceEquals() - which handles nulls for you.

一个有用的快捷方式是使用Object.ReferenceEquals() - 它为您处理空值。

So you could write this instead:

所以你可以这样写:

    // First test
    if (Object.ReferenceEquals(a, b))
    {
        return true;
    }

with the bonus that this also handles the case where a and b are the same exact object.

有奖金,这也处理a和b是同一个确切对象的情况。

Once you get past the Object.ReferenceEquals() test, you know that a and b are different.

一旦超过了Object.ReferenceEquals()测试,就会知道a和b是不同的。

So your next test:

所以你的下一个测试:

    // Second test
    if ((a as object == null && b as object != null)
        || (b as object == null && a as object != null))
    {
        return false;
    }

can be simplified - since you know that if a is null, b cannot be null, and so on.

可以简化 - 因为你知道如果a为null,则b不能为null,依此类推。

    // Second test
    if (Object.Equals(a, null) || Object.Equals(b, null))
    {
        return false;
    }

If this test fails, then you know that a and b are different, and that neither is null. A good time to call your overridden Equals().

如果此测试失败,那么您知道a和b不同,并且两者都不为null。是调用被覆盖的Equals()的好时机。

    // Use the implementation of Equals() for the rest
    return a.Equals(b as object);

#6


public class Foo : IEquatable<Foo>
{
    public Int32 Id { get; set; }

    public override Int32 GetHashCode()
    {
        return this.Id.GetHashCode();
    }

    public override Boolean Equals(Object obj)
    {
        return !Object.ReferenceEquals(obj as Foo, null)
            && (this.Id == ((Foo)obj).Id);

        // Alternative casting to Object to use == operator.
        return ((Object)(obj as Foo) != null) && (this.Id == ((Foo)obj).Id);
    }

    public static Boolean operator ==(Foo a, Foo b)
    {
        return Object.Equals(a, b);
    }

    public static Boolean operator !=(Foo a, Foo b)
    {
        return !Object.Equals(a, b);
    }

    public Boolean Equals(Foo other)
    {
        return Object.Equals(this, other);
    }
}

#7


I have used the following approach and it seemed to work well for me. Infact, Resharper suggests this approach.

我使用了以下方法,它似乎对我有用。事实上,Resharper建议采用这种方法。

public bool Equals(Foo pFoo)
{
        if (pFoo == null)
            return false;
        return (pFoo.Id == Id);
}

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

        return Equals(obj as Foo);
}

#8


I prefer to perform all the comparison logic in the Equals(T) method, and leave the "if this or that is null, else ..." in operator overloads to the framework.

我更喜欢在Equals(T)方法中执行所有比较逻辑,并在运算符重载到框架中留下“if this或that null,else ......”。

The only tricky thing about overriding operator overloads is that you can no longer use those operators in your Equals implementation, for example to compare with null. Instead, object.ReferenceEquals can be used to achieve the same effect.

覆盖运算符重载的唯一棘手的问题是,您不能再在Equals实现中使用这些运算符,例如与null进行比较。相反,object.ReferenceEquals可用于实现相同的效果。

Following the TwoDPoint example in the MSDN Guidelines for Overriding Equals() and Operator == article, this is the pattern I generate when implementing value equality for types:

遵循MSDN指南中的OverDPiding Equals()和Operator ==文章中的TwoDPoint示例,这是我在为类型实现值相等时生成的模式:

public override bool Equals( object obj ) {
  // Note: For value types, would use:
  // return obj is TwoDPoint && this.Equals( (TwoDPoint)obj );
  return this.Equals( obj as TwoDPoint );
}

public bool Equals( TwoDPoint other ) {
  // Note: null check not needed for value types.
  return !object.ReferenceEquals( other, null )
      && EqualityComparer<int>.Default.Equals( this.X, other.X )
      && EqualityComparer<int>.Default.Equals( this.Y, other.Y );
}

public static bool operator ==( TwoDPoint left, TwoDPoint right ) {
  // System.Collections.Generic.EqualityComparer<T> will perform the null checks 
  //  on the operands, and will call the Equals overload if necessary.
  return EqualityComparer<TwoDPoint>.Default.Equals( left, right );
}

public static bool operator !=( TwoDPoint left, TwoDPoint right ) {
  return !EqualityComparer<TwoDPoint>.Default.Equals( left, right );
}

The form above is the safest implementation, as it simply forwards the field equality checks to the framework and requires no knowledge of whether the fields overload the equality operators. It is perfectly fine to simplify this where you know the overload exists:

上面的表单是最安全的实现,因为它只是将字段相等性检查转发到框架,并且不需要知道字段是否重载相等运算符。在您知道存在过载的情况下简化此操作是完全可以的:

public bool Equals( TwoDPoint other ) {
  return !object.ReferenceEquals( other, null )
      && this.X == other.X
      && this.Y == other.Y;
}

You can also replace the EqualityComparer<T> calls in the operator overloads with calls to the static object.Equals method when comparing reference types, or when boxing value types does not matter:

您还可以在运算符重载时替换EqualityComparer 调用,并在比较引用类型时调用静态object.Equals方法,或者在装箱值类型无关紧要时:

public static bool operator ==( TwoDPoint left, TwoDPoint right ) {
  return object.Equals( left, right );
}

public static bool operator !=( TwoDPoint left, TwoDPoint right ) {
  return !object.Equals( left, right );
}

See also What is the best algorithm for an overridden GetHashCode? for implementing GetHashCode.

另请参阅重写的GetHashCode的最佳算法是什么?用于实现GetHashCode。