什么是“最佳实践”为引用类型的两个实例比较?什么是“最佳实践”为引用类型的两个实例比较?(What

2019-05-13 18:43发布

我碰到这个最近来了,到现在为止,我一直愉快地覆盖等号(= =)和/或如果两个引用类型实际上包含相同的数据 (即看起来是一样的,即两个不同的实例)equals方法才能看到。

我一直在使用这种更因为我已经越来越多的自动化测试(比较基准/针对该预期数据返回)。

虽然找了一些在MSDN编码标准指引我读到一篇文章说反对这一提议。 现在我明白为什么文章这样说(因为他们是不一样的情况下 ),但它并没有回答这个问题:

  1. 什么是比较两个引用类型的最佳方式?
  2. 我们应该实现IComparable的 ? (我还见过一提,这应该只值类型保留)。
  3. 有一些接口,我不知道?
  4. 难道只推出我们自己的?

非常感谢^ _ ^

更新

貌似我有错看了一些文件(它是一个漫长的一天)和压倒一切的Equals可能是要走的路..

如果要实现引用类型,你应该考虑重写Equals方法上的参考类型,如果你的类型看起来像一个基本类型,如点,弦乐,BigNumber,等等。 大多数引用类型不应该重载相等运算符, 即使他们重载Equals。 但是,如果要实现该预期具有值语义,如复数类型的引用类型,你应该重写等式操作符。

Answer 1:

它看起来就像你在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()方法是Object类的一部分,你的类重载,如果你想这种类型的功能。

究其原因重载“==”可能是一个坏主意的对象是,通常情况下,你仍然希望能够做到“这些都是相同的指针”的比较。 这些通常是在为依赖,例如,将元素插入其中没有允许重复的列表,如果这个操作是在非标准的方式重载你的一些东西的框架可能无法正常工作。



Answer 2:

正确地实施在.NET平等,高效, 无代码重复是很难的。 具体而言,对于具有值语义引用类型(即不可变类型的治疗equvialence作为平等 ),应实现的System.IEquatable<T>接口 ,你应该执行所有的不同的操作( EqualsGetHashCode==!= ) 。

作为一个例子,这里有一个类实现值相等:

class Point : IEquatable<Point> {
    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);
}


Answer 3:

下面我总结出来的,你需要实现IEquatable并提供来自各个MSDN文档页面的理由时做什么。


摘要

  • 当需要的值相等性测试(如集合使用对象时),你应该实现IEquatable接口,重写的Object.Equals,和GetHashCode为你的类。
  • 当参考平等需要测试,你应该使用==操作符,运算符!=和Object.ReferenceEquals 。
  • 您应该只覆盖operator ==和operator!=的值类型和不可变的引用类型。

理由

IEquatable

所述System.IEquatable接口用于比较是否相等的对象的两个实例。 的对象是基于在类实现的逻辑比较。 在一个布尔值的比较结果指示所述对象是不同的。 这是相比于System.IComparable接口,它返回一个整数,指示对象值如何不同。

该IEquatable接口声明必须重写的两种方法。 equals方法包含执行实际的比较,如果对象值相等,或假,如果他们不返回true实现。 GetHashCode方法应该返回可被用于唯一地识别包含不同值的相同对象的唯一哈希值。 该类型使用哈希算法是实现特定的。

IEquatable.Equals方法

  • 你应该为你的对象来处理,他们将被存储在一个数组或泛型集合的可能性实现IEquatable。
  • 如果要实现IEquatable你也应该重写的Object.Equals(对象)和GetHashCode的基类的实现,使他们的行为与该IEquatable.Equals方法一致

对于忽略equals()和operator ==指引(C#编程指南)

  • x.Equals(x)返回真。
  • x.Equals(y)的返回相同的值作为y.Equals(x)的
  • 如果(x.Equals(Y)&& y.Equals(Z))返回true,那么x.equals(z)返回真。
  • x的连续调用。 等于(Y),只要x和y所引用的对象不被修改返回相同的值。
  • X。 等于(空)(仅适用于非空值类型。欲了解更多信息,请参阅返回false 可空类型(C#编程指南) 。)
  • 新实施的Equals不应该抛出异常。
  • 建议重写任何类的equals也重写Object.GetHashCode。
  • 是建议,除了实现equals(对象),任何类也实现了自己的类型等于(型),以提高性能。

默认情况下,引用相等操作==测试通过确定是否两个引用表示同一个对象。 因此,引用类型没有实现,以获得此功能的==操作符。 当类型是不可变的,即,包含在该实例中的数据不能被改变,重载运算符==比较值相等的,而不是参考平等可以是有用的,因为,作为不可变对象,它们可以被认为是相同的,只要因为它们具有相同的价值。 它不是重写操作符==在非稳定的类型是个好主意。

  • 重载==操作符的实现不应该抛出异常。
  • 该重载==操作符的任何类型也应该重载operator!=。

==运算符(C#参考)

  • 对于预定义的值的类型,如果它的操作数的值相等,否则为假相等运算符(==)返回true。
  • 对于字符串以外的引用类型,==如果两个操作数指的是同一个对象返回true。
  • 对于字符串类型,==比较字符串的值。
  • 当使用您的运营商中== ==比较的覆盖测试空,请确保您使用的基本对象类运营商。 如果不这样做,将出现无限递归导致计算器。

的Object.Equals方法(对象)

如果您的编程语言支持运算符重载,如果您选择重载相等运算符给定类型,该类型必须重写Equals方法。 Equals方法的这种实现必须返回相同的结果平等的运营商

下面的准则是实现值类型

  • 考虑压倒一切等于获得了通过对值类型的Equals的默认实现提供更高的性能。
  • 如果重写Equals和语言支持运算符重载,必须重载相等运算符为你的价值类型。

下面的准则是实现引用类型

  • 考虑如果类型的语义是基于该类型表示一些值(S)的事实覆盖上的参考类型等于。
  • 大多数引用类型不能重载相等运算符,即使他们重载Equals。 但是,如果要实现该预期具有值语义,如复数类型的引用类型,你必须重写相等运算符。

其他陷阱

  • 当重写GetHashCode的(),请确保您的哈希码使用它们之前测试参考类型NULL。
  • 我遇到了与基于接口的编程和操作问题在这里重载描述: 运营商,在C#基于接口的编程超载


Answer 4:

那篇文章只是建议不要重写等式运算符(引用类型),对不忽略equals。 你应该重写你的对象(引用或值)范围内的Equals如果相等检查将意味着比基准检查的更多的东西。 如果你想要一个接口,还可以实现IEquatable (由泛型集合使用)。 如果实现IEquatable,但是,你也应该重写equals,作为IEquatable说明部分指出:

如果实现IEquatable <T>,还应该重写的Object.Equals(对象)和GetHashCode的基类的实现使得它们的行为与该IEquatable <T> .Equals方法一致。 如果你重写的Object.Equals(对象),你重写的实现也称为调用静态等于(System.Object的,System.Object的)方法的类。 这确保了equals方法的所有调用返回一致的结果。

在关于是否应该实现equals和/或等于运算符:

从实现equals方法

大多数引用类型不应该重载相等运算符,即使他们重载Equals。

从准则实现equals和等号(= =)

覆盖每当你实现等号(==),并让他们做同样的事情Equals方法。

这只能说,你需要,只要你实现平等的运营商来覆盖平等。 这并不是说,你需要的时候改变了equals重载相等运算符。



Answer 5:

对于复杂的对象,这将产生具体的比较,然后实现了IComparable和界定的比较方法的比较是一个很好的实现。

比如我们有“车”的物件,唯一的区别可能是注册号,我们用它来比较,以确保测试返回预期值是我们想要的。



Answer 6:

我倾向于使用什么ReSharper的自动进行。 例如,它自动创建为我的引用类型中的一种:

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

public bool Equals(SecurableResourcePermission obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    return obj.ResourceUid == ResourceUid && Equals(obj.ActionCode, ActionCode) && Equals(obj.AllowDeny, AllowDeny);
}

public override int GetHashCode()
{
    unchecked
    {
        int result = (int)ResourceUid;
        result = (result * 397) ^ (ActionCode != null ? ActionCode.GetHashCode() : 0);
        result = (result * 397) ^ AllowDeny.GetHashCode();
        return result;
    }
}

如果要覆盖==和还是做裁判的检查,你仍然可以使用Object.ReferenceEquals



Answer 7:

微软似乎已经改变了调子,或者至少有关于未重载相等运算符冲突的信息。 根据这个微软的文章标题为如何:定义值相等的类型:

“的==和!=操作员可以用类使用,即使该类不超载他们。但是,默认行为是执行一个参考平等检查。在一类,如果你重载Equals方法,你应该重载==和!=运营商,但它不是必需的。”

据埃里克利珀他回答一个问题我问了一下在C#中的平等最少的代码 -他说:

“你遇到了这样做的危险是,你得到你所定义的==操作符,默认情况下不参考平等,你可以很容易地在一个情况下重载equals方法做价值平等和==做参考平等结束,然后你不小心使用上没有参考相等的东西都是价值相等参考平等。这是一个容易出错的做法,是很难被人类代码审查发现。

几年前,我曾在一个静态分析算法来统计检测到这种情况,我们发现每百万行代码大约两个实例的跨越,我们研究了所有的代码库的缺陷率。 当考虑只是代码库其中有某处覆盖平等相待,缺陷率明显要高得多!

此外,考虑成本VS风险。 如果你已经拥有了IComparable的实现,然后写所有的运营商是微不足道的俏皮话,不会有错误,永远不会改变。 这是你曾经打算写的最便宜的代码。 如果预定写入固定成本和测试十几微小的方法VS发现和修复的无限成本之间选择一个难以看到的臭虫参考平等是用来代替价值平等的,我知道我会选哪一个。”

.NET框架永远不会使用==或!=与您编写的任何类型。 但是,危险的是,如果别人确实会发生什么。 因此,如果类是第三方,那么我将永远提供==和!=运算符。 如果类仅用于由集团内部使用,我仍然可能实现==和!=运算符。

我只执行<,<=,>,和> =如果运营商IComparable的实施。 在这样的SortedSet有序通用容器中使用排序或当类似 - 如果类型需要支持排序IComparable的只应实施。

如果该组或公司的地方,不打算实现的==有政策和=操作 - !那我当然会遵循该政策。 如果这样的策略已经到位,那么这将是明智与Q / A码分析工具,它标志了==任何发生,并执行它!=与参考型使用时,操作员。



Answer 8:

我相信越来越作为检查对象平等正确的,因为简单的东西是有点棘手使用.NET的设计。

对于结构

1)实施IEquatable<T> 这显着提高了性能。

2)既然你有你自己Equals现在,重写GetHashCode ,并能够与各种平等检查覆盖相一致object.Equals为好。

3)重载==!=操作者无需做宗教,因为编译器会发出警告,如果你无意中等同于另一个结构用==!= ,但其良好这样做是一致Equals方法。

public struct Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is Entity))
            return false;

        return Equals((Entity)obj);
    }

    public static bool operator ==(Entity e1, Entity e2)
    {
        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

上课

从MS:

大多数引用类型不应该重载相等运算符,即使他们重载Equals。

对我来说==感觉值相等,更像是一个语法糖Equals方法。 编写a == b比写更直观a.Equals(b) 很少,我们需要检查引用相等。 在抽象的层次处理物理对象的逻辑表示这不是我们需要检查。 我认为有不同的语义==Equals实际上是混乱。 我相信它应该已经==为价值平等Equals参考(或类似一个更好的名字IsSameAs )平等摆在首位。 我很想不采取MS方针认真这里,不只是因为它是不自然的我,也是因为超载==没有做什么大的伤害。 这不像不重写非泛型EqualsGetHashCode能咬回来,因为框架不使用==任何地方,但只有当我们我们自己使用它。 我从没有得到过载的唯一的真正的好处==!=会超过我没有控制整个框架的设计一致性。 而这确实是一个很大的事情, 所以我黯然会坚持下去

参考语义(可变对象)

1)覆盖EqualsGetHashCode

2)实施IEquatable<T>不是必须的,但将是很好,如果你有一个。

public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

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

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

用值语义(不可变的对象)

这是棘手的部分。 可以很容易被搞砸了,如果不采取照顾..

1)覆盖EqualsGetHashCode

2)过载==!=匹配Equals请确保它为空值

2)实施IEquatable<T>不是必须的,但将是很好,如果你有一个。

public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

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

    public static bool operator ==(Entity e1, Entity e2)
    {
        if (ReferenceEquals(e1, null))
            return ReferenceEquals(e2, null);

        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

请特别注意,看看它如何票价如果你的类可以继承,在这种情况下,你必须确定一个基类对象可以等于一个派生类对象。 理想地,如果用于检查平等没有派生类的对象,那么基类的实例可以等于派生类实例,并在这种情况下,没有必要检查Type在通用平等Equals基类。

一般来说,请注意不要重复的代码。 我可以做出一个通用的抽象基类( IEqualizable<T>左右)作为模板,允许重复使用更容易,但遗憾的是在阻止我从其他类派生C#。



Answer 9:

所有的答案以上不考虑多态,往往你想得出引用时要使用通过基本参考比较派生甚至平等的。 请在这里看到的问题/讨论/回答- 平等和多态性



文章来源: What is “Best Practice” For Comparing Two Instances of a Reference Type?