IEquatable<T> - C#中重写.Equals(object obj)的最佳实践

4
每当我编写一个新的可能需要比较数据的类或结构体时,我总是实现IEquatable接口,因为这会为类/结构体提供强类型的.Equals(T other)方法。
示例:
public struct Radius : IEquatable<Radius>
{
    public Int32 TopLeft { get; set; }
    public Int32 TopRight { get; set; }
    public Int32 BottomLeft { get; set; }
    public Int32 BottomRight { get; set; }

    public bool Equals(Radius other)
    {
        return this.TopLeft == other.TopLeft
            && this.TopRight == other.TopRight
            && this.BottomLeft == other.BottomLeft
            && this.BottomRight == other.BottomRight;
    }
}

除了提供.Equals(Radius other)的实现外,我还应该覆盖默认实现(.Equals(object obj))。

我有两个选择,我的问题是,哪个实现更好?

选项1是使用强制转换:

public override bool Equals(object obj)
{
    return this.Equals((Radius)obj);
}

选项2是使用"as"关键字:
public override bool Equals(object obj)
{
    return this.Equals(obj as Radius);
}

我的提问是,使用强制转换会抛出异常,如果obj不能转换为Radius,而as则会解析为null,如果不可转换,则只需将thisnull进行比较,而不会抛出异常。因此,是抛出异常还是只返回false更好呢?
编辑:正如许多SO用户所指出的那样,结构体不能为null,因此第二个选项不适用于结构体。因此另一个问题浮现:对于结构体和类,重写实现.Equals(object obj)应该相同吗?

1
你的 Equals(Radius) 方法如果 other 为 null,应该返回 false,否则将会抛出空输入的异常。 - cdhowie
@Servy,我还没有测试过,但为什么不用“obj as Radius?” - Matthew Layton
@cdhowie 是的,可以这样做,但是这样做并没有真正的优势(或劣势)。额外的类型检查和额外的空值检查一样耗费资源。 - Servy
@series0ne 因为 Equals 方法不接受 Radius? 类型的参数,它需要一个 Radius 类型的参数。 - Servy
1
@cdhowie 一种最快速操作的速度提高了30%。这在性能上是完全可以忽略不计的差异,以至于在几乎任何应用程序中都不值得提及。如果类型本身可为空,并且您想要可空结果,则使用as是有意义的,但是由于它在逻辑上不可为空,因此您所做的工作实际上消除了获得的优势。这两个解决方案相当等效,差异几乎完全是主观偏好,没有更多的区别。 - Servy
显示剩余6条评论
3个回答

7
Equals() 方法绝不能抛出异常
不同类型的对象只是不相等。
引用文档

Equals 的实现不应抛出异常;它们应该始终返回一个值。例如,如果 obj 为 null,则 Equals 方法应返回 false 而不是抛出 ArgumentNullException。


1
@chriswarner:在这种情况下,在进入Equals()之前就会抛出异常。 - SLaks
@SLaks,当然啦:s 我正在查看String.Equals的反编译源代码,您知道为什么要进行null检查吗?这只是一种“保险措施”吗? - chris warner
1
@chriswarner:源代码中写道 if (this == null) //此处必须进行保护,以防止反向P/Invoke和其他不使用callvirt指令的调用方 throw new NullReferenceException(); //引发空引用异常 - SLaks
1
@SLaks 当使用指针时,这与其有很大关系。在安全代码中,不需要进行空值测试,因为使用空对象的成员将引发NullReferenceException异常,在破损的代码面前是完全可以接受的。由于对空对象进行指针算术运算而导致的访问冲突是不可接受的。 - cdhowie
我不打算在这里抛出异常。请记住,您并不总是有意识地知道何时调用此方法,并且在许多可能调用它的情况下都不会期望异常。 - Thulani Chivandikwa
显示剩余12条评论

5
正如@SLaks已经提到的,Equals()不应该抛出异常。
在这种特殊情况下,我认为使用is运算符与类型转换应该能帮助你解决问题:
public override bool Equals(object obj)
{
     if(obj is Radius)
         return Equals((Radius)obj);

     return false;
}

如果你有一个class,你应该简单地使用as操作符:

public override bool Equals(object obj)
{
     return Equals(obj as MyObj);
}

public bool Equals(MyObj obj)
{
     if(ReferenceEquals(obj, null))
         return false;

     // ToDo: further checks for equality.
}

-1

我的个人意见是使用第二个选项,甚至事先检查对象是否为“半径”,然后返回false,以使意图更清晰。


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