我碰到这个最近,直到现在我已经快乐的压倒一切的平等操作员(==)和/或 等于 方法,以便看看如果两个参考文献的类型实际上包含相同的 数据 (即两个不同的实例中,看起来相同)。

我一直使用这个甚至更多,因为我已经获得更多来自动化测试(比较基准/期望的数据,返回).

虽然寻找一些的 编码标准的指导方针在MSDN 我遇到了一个 文章 建议反对它。现在我明白了 为什么 该条是这样说的(因为他们是不一样的 实例),但是它没有回答这个问题:

  1. 什么是最好的方式比较两个参考的类型?
  2. 我们应该实现 ?(我也看见提及,这应该是保留价值的类型)。
  3. 有一些接口我不知道的吗?
  4. 我们应该只是滚了我们自己的?!

多谢了^_^

更新

看起来像我有误读的一些文件(这是一个漫长的一天)和压倒一切的 等于 可能会去..

如果你正在实施的参考 类型时,应当考虑压倒一切的 等方法上的基准类型 如果你看起来像一个基本类型 如一点,String,BigNumber, 等等。大多数参照类型应该 不过载 平等 操作员, 甚至 如果他们替代等于.但是, 如果你正在实施一个参考 类型,旨在以具有价值 语义,例如一个复杂的多 类型,你应该复盖的平等 操作员。

有帮助吗?

解决方案

看起来你正在使用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 类的一部分,如果你想要这种类型的功能,你的类会重载它。

重载'=='的原因对于对象来说可能是一个坏主意,通常,你仍然希望能够做出“这些是同一个指针”吗?比较。这些通常依赖于,例如,将元素插入到不允许重复的列表中,如果此运算符以非标准方式过载,则某些框架内容可能无效。

其他提示

正确,高效地实现.NET中的相等性和无代码重复很难实现。具体来说,对于具有值语义的引用类型(即将equatilence视为平等的不可变类型),你应该实现 System.IEquatable< T> 接口,你应该实现所有不同的操作( Equals GetHashCode == != )。

作为一个例子,这里是一个实现价值平等的类:

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(指向其他)中的第二行和 GetHashCode()方法。其他代码应保持不变。

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

代码故意等同于派生类类型的偶数对象。通常,这可能不合适,因为基类和派生类之间的相等性没有明确定义。不幸的是,.NET和编码指南在这里并不十分清楚。 Resharper创建的代码,在另一个答案中,在这种情况下很容易出现意外行为,因为 Equals (对象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);
}

下面我总结出来你需要做什么时实现就不能修改和提供的理由各种MSDN文件的网页。


摘要

  • 在测试对于价值的平等,是需要(例如在使用的对象在集)应该执行的就不能修改的接口,复盖的对象。平等和GetHashCode为您类。
  • 当测试的参考平等是希望你应该使用算==,操!= 和 对象。ReferenceEquals.
  • 你应该仅仅复盖操作者==和操作者!= 对于 Valuetype 和不可变的基准类型。

理由

就不能修改

该系统。就不能修改的接口是用来比较两个实例的对象的平等。的对象,比较的基础上实现的逻辑。比较结果在布尔的价值表示,如果对象是不同的。这是相对于该系统。型接口,返回的一个整数指示如何对象的价值观是不同的。

这就不能修改的界面宣布的两种方法必须重写。等方法包含实施,以执行实际的比较和返回的真实的,如果对象的价值是相等的,或假如他们不是。GetHashCode方法应该返回一个独特的散列值,可以使用的唯一标相同的对象包含不同的价值观。这种类型的散列算法的使用是实现特定的。

就不能修改.平等的方法

  • 你应该实现就不能修改为对象来处理这种可能性,他们将被保存在一系列或一般收集。
  • 如果实现就不能修改还应该复盖基类实现的对象。平等(目的)和GetHashCode使他们的行为是一贯的,就不能修改.平等的方法

准则》压倒一切的等()以及操作者==(C#编程指南)

  • x.等(x)返回正确的。
  • x.等(y)返回相同的价值为y。等(x)
  • 如果(x。等(y)&&y。等(z))返回真的,那么x。等(z)返回正确的。
  • 连续调用的x。等(y)返回相同的价值,只要该物体所引用的x和y不作修改。
  • x.等(null)returns false(为非空值类型只。更多信息,请参阅 空类型(C#编程指南).)
  • 新的执行等于不应该扔例外情况。
  • 它建议的任何类替代等于也复盖的对象。GetHashCode.
  • 是的是建议,除了实现平等(目标),任何一类,也实现平等(类型),为他们自己的类型,以提高性能。

默认情况下,操作者==试验供参考的平等,通过确定是否两个参考文献表示同样的对象。 因此,参照类型没有实施操作员==为了获得这一功能。当一个类型是不可改变的,也就是说,数据中包含的实例不能更改,载操作员==比较价值的平等,而不是参照平等可能是有用的,因为不可改变的对象,他们可以被认为是相同的,只要他们有同样的价值。 它不是一个好主意,以复盖操作者==非不可改变类型。

  • 超负载操作员==的实现不应该扔例外情况。
  • 任何类型的重载操作员==还应载操作者!=.

==Operator(C#参考)

  • 对于预先定义的价值的类型、平等操作员(==)返回真正的如果它的操作数都是平等的,假。
  • 为参照其他类型比string,==返回真正的如果它的两个操作数指相同的对象。
  • 为串的类型,==比较的价值观。
  • 测试时,对使用空==比较内的运营商=="替代",确保使用的基本物流运营商。如果你不限递归将发生的导致一个计算器.

对象。平等的方法(目标)

如果编程语言支持操作人员负载过重,如果你选择超载的平等操作者对于给定的类型,此类必须复盖等方法。这种实现平等的方法必须返回相同的结果的平等操作员

以下准则的实施 值的类型:

  • 考虑的首要等到增强的性能,提供的默认实现平等的关ValueType.
  • 如果复盖等和语言支持操作人员负载过重,必须载的平等操作人员对于你的价值的类型。

以下准则的实施 基准类型:

  • 考虑的首要等上一个基准类型,如果该义的类型是基于这样的事实,表示一些值(s)。
  • 大多数参照类型必须不超载的平等的操作者,即使他们复盖平等的。然而,如果实现的基准类型,目的是有价值的语义、例如一个复杂的多种类型,你必须复盖的平等操作员。

额外的陷阱

该文章建议不要覆盖等于运算符(对于引用类型),而不是覆盖重写等号。如果相等检查不仅仅意味着检查,那么您应该在对象(引用或值)中覆盖Equals。如果你想要一个界面,你也可以实现 IEquatable (由通用集合)。但是,如果确实实现了IEquatable,则还应该重写equals,因为IEquatable备注部分指出:

  

如果实现IEquatable&lt; T&gt;,则还应覆盖Object.Equals(Object)和GetHashCode的基类实现,以使它们的行为与IEquatable&lt; T&gt; .Equals方法的行为一致。如果您重写了Object.Equals(Object),则在调用类上的静态Equals(System.Object,System.Object)方法时也会调用重写的实现。这确保了Equals方法的所有调用都返回一致的结果。

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

来自实施平等方法

  

大多数引用类型不应重载相等运算符,即使它们重写等于。

来自实施平等及平等操作员指南( ==)

  

每当实现相等运算符(==)时重写Equals方法,并使它们执行相同的操作。

这只表示每当实现相等运算符时都需要重写Equals。 表示在重写Equals时需要覆盖相等运算符。

对于将产生特定比较的复杂对象,然后实现IComparable并在Compare方法中定义比较是一个很好的实现。

例如,我们有“车辆”。唯一差异可能是注册号的对象,我们使用它来进行比较,以确保测试中返回的期望值是我们想要的值。

我倾向于使用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;
    }
}

如果要覆盖 == 并仍然进行ref检查,仍然可以使用 Object.ReferenceEquals

Microsoft似乎已经改变了他们的调整,或者至少存在关于不重载相等运算符的冲突信息。根据微软文章标题为如何:定义价值平等类型:

&quot; ==和!=运算符可以与类一起使用,即使该类没有超载它们。但是,默认行为是执行引用相等性检查。在类中,如果重载Equals方法,则应该重载==和!=运算符,但这不是必需的。“

根据Eric Lippert的回答我问过的问题 C#中平等的最小代码 - 他说:

&quot;你遇到的危险是你得到一个为你定义的==运算符,它默认引用相等。您可以很容易地在一个重载的Equals方法确实值相等并且==确实引用相等的情况下结束,然后您不小心在值相等的非引用相等的事物上使用引用相等。这是一种容易出错的做法,人工代码审查很难发现。

几年前,我使用静态分析算法来统计检测这种情况,我们发现在我们研究的所有代码库中,每百万行代码中有大约两个实例的缺陷率。当只考虑具有某些被覆盖的Equals的代码库时,缺陷率显然要高得多!

此外,考虑成本与风险。如果您已经拥有IComparable的实现,那么编写所有运算符都是微不足道的单行程序,它们不会有错误并且永远不会被更改。这是你要编写的最便宜的代码。如果在编写的固定成本和测试十几种微小方法之间做出选择,而不是找到并修复难以看到的错误的无限成本,其中使用引用相等而不是值相等,我知道我会选择哪一个。 ;

.NET Framework永远不会使用您编写的任何类型的==或!=。但是,如果其他人这样做会发生危险。因此,如果该类用于第三方,那么我将始终提供==和!=运算符。如果该类仅用于组内部使用,我仍然可能实现==和!=运算符。

如果实现了IComparable,我只会实现&lt;,&lt; =,&gt;和&gt; =运算符。只有当类型需要支持排序时才应该实现IComparable - 比如在排序或在SortedSet等有序通用容器中使用时。

如果集团或公司制定了一项政策,以便不实施==和!=运营商 - 那么我当然会遵循该政策。如果这样的策略到位,那么使用Q / A代码分析工具强制执行它是明智的,该工具在与引用类型一起使用时标记==和!=运算符的任何出现。

我认为在.NET的设计中,获取像检查对象一样简单正确的东西是有点棘手的。

对于Struct

1)实现 IEquatable&lt; T&gt; 。它显着提高了性能。

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 方法的语法糖。编写 a == b 比编写 a.Equals(b)更直观。我们很少需要检查参考平等。在处理物理对象的逻辑表示的抽象级别中,这不是我们需要检查的。我认为为 == Equals 设置不同的语义实际上可能令人困惑。我认为它应该是 == 用于值相等,而 Equals 用于引用(或者更好的名称,如 IsSameAs )首先是相等的。 我很想在此不认真对待MS指南,不仅因为它对我来说不自然,而且因为重载 == 不会造成任何重大伤害。这与不重写非泛型 Equals GetHashCode 不同,因为框架不会在任何地方使用 == ,但仅限于我们自己用它。我从没有重载 == != 获得的唯一真正的好处将是整个框架的设计与我没有的一致性控制。这确实是一件大事,很遗憾我会坚持下去

使用引用语义(可变对象)

1)覆盖 Equals GetHashCode

2)实现 IEquatable&lt; T&gt; 不是必须的,但是如果你有的话会很好。

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)覆盖 Equals GetHashCode

2)重载 == != 以匹配 Equals 确保它适用于空值

2)实现 IEquatable&lt; T&gt; 不是必须的,但是如果你有的话会很好。

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 相等性代码>基类的等于。

一般注意不要重复代码。我可以创建一个通用的抽象基类( IEqualizable&lt; T&gt; 左右)作为模板,以便更容易重用,但遗憾的是在C#中阻止我从其他类派生。

上面的所有答案都没有考虑多态性,即使通过基准引用进行比较,通常也希望派生引用使用派生的Equals。请在此处查看问题/讨论/答案 - 平等和多态

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top