当前位置:  开发笔记 > 编程语言 > 正文

什么是比较参考类型的两个实例的"最佳实践"?

如何解决《什么是比较参考类型的两个实例的"最佳实践"?》经验,为你挑选了3个好方法。

我最近遇到过这种情况,到目前为止,我一直在愉快地重写等于运算符(==)和/或Equals方法,以查看两个引用类型是否实际包含相同的数据(即两个看起来相同的不同实例).

我一直在使用它,因为我已经进行了更多的自动化测试(比较参考/预期数据与返回的数据).

在查看MSDN中的一些编码标准指南时,我遇到了一篇建议反对它的文章.现在我理解为什么文章说这个(因为它们不是同一个实例)但它没有回答这个问题:

    比较两种参考类型的最佳方法是什么?

    我们应该实施IComparable吗?(我还看到提到这应该仅为值类型保留).

    有一些我不知道的界面吗?

    我们应该自己动手吗?!

非常感谢^ _ ^

更新

看起来我错误地阅读了一些文档(这是漫长的一天)并且压倒Equals可能是要走的路.

如果要实现引用类型,则应考虑在引用类型上覆盖Equals方法(如果类型看起来像基本类型,如Point,String,BigNumber等).大多数引用类型不应重载等于运算符,即使它们重写等于.但是,如果要实现旨在具有值语义的引用类型(例如复数类型),则应覆盖相等运算符.

Konrad Rudol.. 26

正确,高效地实现.NET中的相等性并且没有代码重复是很困难的.具体而言,值语义的引用类型(即不可变类型的治疗equvialence作为平等的),你应该实现的System.IEquatable接口,你应该实现所有不同的操作(Equals,GetHashCode==,!=).

举个例子,这是一个实现值相等的类:

class Point : IEquatable {
    public int X { get; }
    public int Y { get; }

    public Point(int x = 0, int y = 0) { X = x; Y = y; }

    public bool Equals(Point other) {
        if (other is null) return false;
        return X.Equals(other.X) && Y.Equals(other.Y);
    }

    public override bool Equals(object obj) => Equals(obj as Point);

    public static bool operator ==(Point lhs, Point rhs) => object.Equals(lhs, rhs);

    public static bool operator !=(Point lhs, Point rhs) => ! (lhs == rhs);

    public override int GetHashCode() => X.GetHashCode() ^ Y.GetHashCode();
}

上面代码中唯一可移动的部分是粗体部分:第二行Equals(Point other)GetHashCode()方法.其他代码应保持不变.

对于不表示不可变值的引用类,请不要实现运算符==!=.相反,使用它们的默认含义,即比较对象标识.

代码故意等同于派生类类型的偶数对象.通常,这可能不合适,因为基类和派生类之间的相等性没有明确定义.不幸的是,.NET和编码指南在这里并不十分清楚.Resharper 在另一个答案中发布的代码在这种情况下很容易受到不良行为的影响,因为Equals(object x)并且Equals(SecurableResourcePermission x) 会以不同的方式处理这种情况.

要更改此行为,必须在Equals上面的强类型方法中插入其他类型检查:

public bool Equals(Point other) {
    if (other is null) return false;
    if (other.GetType() != GetType()) return false;
    return X.Equals(other.X) && Y.Equals(other.Y);
}

谢谢Konrad,我们应该在ReSharper中修复它 (2认同)

对于类,当System.Object基类默认提供该功能时,为什么要重写等于和不等于运算符来执行引用比较? (2认同)


Matt J.. 23

看起来你正在使用C#进行编码,它有一个你的类应该实现的Equals方法,如果你想使用一些其他指标来比较两个对象,而不是"这两个指针(因为对象句柄就是指针)相同的内存地址?"

我从这里抓取了一些示例代码:

class TwoDPoint : System.Object
{
    public readonly int x, y;

    public TwoDPoint(int x, int y)  //constructor
    {
        this.x = x;
        this.y = y;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter is null return false.
        if (obj == null)
        {
            return false;
        }

        // If parameter cannot be cast to Point return false.
        TwoDPoint p = obj as TwoDPoint;
        if ((System.Object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public bool Equals(TwoDPoint p)
    {
        // If parameter is null return false:
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public override int GetHashCode()
    {
        return x ^ y;
    }
}

Java具有非常相似的机制.该的equals()方法是一部分对象类,和你的类重载,如果你想这种类型的功能.

重载'=='的原因对于对象来说可能是一个坏主意,通常,您仍然希望能够执行"这些是相同的指针"比较.这些通常依赖于,例如,将元素插入到不允许重复的列表中,并且如果此运算符以非标准方式过载,则某些框架内容可能不起作用.



1> Konrad Rudol..:

正确,高效地实现.NET中的相等性并且没有代码重复是很困难的.具体而言,值语义的引用类型(即不可变类型的治疗equvialence作为平等的),你应该实现的System.IEquatable接口,你应该实现所有不同的操作(Equals,GetHashCode==,!=).

举个例子,这是一个实现值相等的类:

class Point : IEquatable {
    public int X { get; }
    public int Y { get; }

    public Point(int x = 0, int y = 0) { X = x; Y = y; }

    public bool Equals(Point other) {
        if (other is null) return false;
        return X.Equals(other.X) && Y.Equals(other.Y);
    }

    public override bool Equals(object obj) => Equals(obj as Point);

    public static bool operator ==(Point lhs, Point rhs) => object.Equals(lhs, rhs);

    public static bool operator !=(Point lhs, Point rhs) => ! (lhs == rhs);

    public override int GetHashCode() => X.GetHashCode() ^ Y.GetHashCode();
}

上面代码中唯一可移动的部分是粗体部分:第二行Equals(Point other)GetHashCode()方法.其他代码应保持不变.

对于不表示不可变值的引用类,请不要实现运算符==!=.相反,使用它们的默认含义,即比较对象标识.

代码故意等同于派生类类型的偶数对象.通常,这可能不合适,因为基类和派生类之间的相等性没有明确定义.不幸的是,.NET和编码指南在这里并不十分清楚.Resharper 在另一个答案中发布的代码在这种情况下很容易受到不良行为的影响,因为Equals(object x)并且Equals(SecurableResourcePermission x) 会以不同的方式处理这种情况.

要更改此行为,必须在Equals上面的强类型方法中插入其他类型检查:

public bool Equals(Point other) {
    if (other is null) return false;
    if (other.GetType() != GetType()) return false;
    return X.Equals(other.X) && Y.Equals(other.Y);
}


谢谢Konrad,我们应该在ReSharper中修复它
对于类,当System.Object基类默认提供该功能时,为什么要重写等于和不等于运算符来执行引用比较?

2> Matt J..:

看起来你正在使用C#进行编码,它有一个你的类应该实现的Equals方法,如果你想使用一些其他指标来比较两个对象,而不是"这两个指针(因为对象句柄就是指针)相同的内存地址?"

我从这里抓取了一些示例代码:

class TwoDPoint : System.Object
{
    public readonly int x, y;

    public TwoDPoint(int x, int y)  //constructor
    {
        this.x = x;
        this.y = y;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter is null return false.
        if (obj == null)
        {
            return false;
        }

        // If parameter cannot be cast to Point return false.
        TwoDPoint p = obj as TwoDPoint;
        if ((System.Object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public bool Equals(TwoDPoint p)
    {
        // If parameter is null return false:
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public override int GetHashCode()
    {
        return x ^ y;
    }
}

Java具有非常相似的机制.该的equals()方法是一部分对象类,和你的类重载,如果你想这种类型的功能.

重载'=='的原因对于对象来说可能是一个坏主意,通常,您仍然希望能够执行"这些是相同的指针"比较.这些通常依赖于,例如,将元素插入到不允许重复的列表中,并且如果此运算符以非标准方式过载,则某些框架内容可能不起作用.


这实际上是C#的弱点之一.但是,只要实现者遵循这些指导原则,这不是问题,因为对于相同的引用,"=="的语义不会改变.不过,我发现自己在C#中的关键情况下使用`object.ReferenceEquals`(VB有`Is`代替).

3> Zach Burling..:

下面我总结了实现IEquatable时需要做的事情,并提供了各种MSDN文档页面的理由.


摘要

当需要测试值相等时(例如在集合中使用对象时),您应该为您的类实现IEquatable接口,覆盖Object.Equals和GetHashCode.

当需要测试参考相等性时,您应该使用operator ==,operator!=和Object.ReferenceEquals.

您应该只覆盖operator ==和operator!=的值类型和不可变的引用类型.


理由

IEquatable

System.IEquatable接口用于比较对象的两个实例是否相等.根据类中实现的逻辑比较对象.比较结果是一个布尔值,表示对象是否不同.这与System.IComparable接口形成对比,后者返回一个整数,指示对象值的不同之处.

IEquatable接口声明了必须重写的两个方法.Equals方法包含执行实际比较的实现,如果对象值相等则返回true,否则返回false.GetHashCode方法应返回唯一的哈希值,该哈希值可用于唯一标识包含不同值的相同对象.使用的散列算法类型是特定于实现的.

IEquatable.Equals方法

您应该为对象实现IEquatable,以处理它们存储在数组或泛型集合中的可能性.

如果实现IEquatable,还应该覆盖Object.Equals(Object)和GetHashCode的基类实现,以便它们的行为与IEquatable.Equals方法的行为一致.

覆盖等于()和运算符的指南==(C#编程指南)

x.Equals(x)返回true.

x.Equals(y)返回与y.Equals(x)相同的值

if(x.Equals(y)&& y.Equals(z))返回true,则x.Equals(z)返回true.

连续调用x.只要未修改x和y引用的对象,Equals(y)就会返回相同的值.

X.Equals(null)返回false(仅适用于非可空值类型.有关更多信息,请参阅Nullable Types(C#编程指南).)

Equals的新实现不应该抛出异常.

建议任何覆盖Equals的类也会覆盖Object.GetHashCode.

建议除了实现Equals(object)之外,任何类还为自己的类型实现Equals(type),以增强性能.

默认情况下,operator ==通过确定两个引用是否指示同一对象来测试引用相等性.因此,引用类型不必实现operator ==以获得此功能.当一个类型是不可变的,也就是说,实例中包含的数据不能改变时,重载operator ==来比较值的相等而不是引用相等可能是有用的,因为作为不可变对象,它们可以被认为是相同的因为它们具有相同的价值.在非不可变类型中覆盖operator ==不是一个好主意.

重载的operator ==实现不应该抛出异常.

任何重载operator ==的类型也应该重载operator!=.

==运算符(C#参考)

对于预定义的值类型,如果操作数的值相等,则相等运算符(==)返回true,否则返回false.

对于除string之外的引用类型,如果其两个操作数引用同一对象,则==返回true.

对于字符串类型,==比较字符串的值.

在运算符==覆盖中使用==比较测试null时,请确保使用基础对象类运算符.如果不这样做,将发生无限递归,从而导致堆栈溢出.

Object.Equals方法(对象)

如果您的编程语言支持运算符重载,并且您选择重载给定类型的相等运算符,则该类型必须覆盖Equals方法.Equals方法的此类实现必须返回与相等运算符相同的结果

以下准则用于实现值类型:

考虑重写Equals以获得比ValueType上的Equals的默认实现所提供的性能更高的性能.

如果重写等于并且语言支持运算符重载,则必须为值类型重载等于运算符.

以下准则用于实现引用类型:

如果类型的语义基​​于类型表示某些值的事实,请考虑在引用类型上覆盖Equals.

大多数引用类型都不能重载等于运算符,即使它们重写等于.但是,如果要实现该预期具有值语义,如复数类型的引用类型,你必须重写相等运算符.


额外的陷阱

重写GetHashCode()时,请确保在哈希代码中使用它们之前测试NULL的引用类型.

我遇到了这里描述的基于接口的编程和运算符重载的问题:C#中基于接口的编程的运算符重载

推荐阅读
Chloemw
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有