比较引用类型的两个实例的"最佳实践"是什么?
我最近遇到了这个问题,直到现在我一直很高兴地重写了equals运算符(==)和/或者Equals方法,以查看两个引用类型是否实际上包含相同的数据(即两个看起来相同的不同实例)。
我一直在使用这种更因为我已经越来越多的自动化测试(比较基准/针对该预期数据返回)。
虽然找过一些在MSDN我的编码标准指引来到跨反对这一提议的文章。现在,我明白了为什么文章这么说了(因为它们不是同一实例),但是没有回答这个问题:
- 比较两种参考类型的最佳方法是什么?
- 我们应该实现IComparable吗? (我也看到提到,这应该只保留给值类型)。
- 有一些我不知道的界面吗?
- 我们应该自己滚动吗?
非常感谢^ _ ^
更新
似乎我误读了一些文档(这已经是漫长的一天),而覆盖Equals可能是可行的方法。
If you are implementing reference types, you should consider overriding the Equals method on a reference type if your type looks like a base type such as a Point, String, BigNumber, and so on. Most reference types should not overload the equality operator, even if they override Equals. However, if you are implementing a reference type that is intended to have value semantics, such as a complex number type, you should override the equality operator.
解决方案
对于将产生特定比较的复杂对象,则实现IComparable并在Compare方法中定义比较是一个很好的实现。
例如,我们有"车辆"对象,其中唯一的区别可能是注册号,我们使用它进行比较以确保测试中返回的期望值是我们想要的值。
似乎我们正在用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类的一部分,如果我们需要这种类型的功能,则类会将其重载。
究其原因重载"=="可以为对象的一个坏主意的是,通常情况下,你仍然希望能够做到"这些都是相同的指针"的比较。这些通常用于例如将元素插入到不允许重复的列表中,并且如果此操作符以非标准方式重载,则某些框架内容可能无法正常工作。
该文章仅建议不要重写相等运算符(对于引用类型),而不是重写等于。如果相等检查比引用检查更有意义,则应在对象(引用或者值)中覆盖Equals。如果需要接口,还可以实现IEquatable(由通用集合使用)。但是,如果我们确实实现IEquatable,则还应重写等于,如IEquatable备注部分所述:
If you implement IEquatable<T>, you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behavior is consistent with that of the IEquatable<T>.Equals method. If you do override Object.Equals(Object), your overridden implementation is also called in calls to the static Equals(System.Object, System.Object) method on your class. This ensures that all invocations of the Equals method return consistent results.
关于是否应实现等于和/或者等于运算符:
从实现equals方法
Most reference types should not overload the equality operator, even if they override Equals.
摘自《实施平等准则和平等经营者》(==)
Override the Equals method whenever you implement the equality operator (==), and make them do the same thing.
这仅表示我们在实现均等运算符时需要覆盖均等。它并不表示我们在覆盖Equals时就需要覆盖equals运算符。
我倾向于使用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"。
在.NET中正确,有效地实现平等并且没有代码重复是很困难的。具体来说,对于具有值语义的引用类型(即,将等价性视为相等的不可变类型),应实现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(Point other)中的第二行和GetHashCode()方法中的第二行。其他代码应保持不变。
对于不代表不变值的参考类,不要实现运算符==
和!=
。而是使用它们的默认含义,即比较对象标识。
该代码甚至将派生类类型的对象等同起来。通常,由于基类和派生类之间的相等性定义不明确,这可能不是理想的。不幸的是,.NET和编码指南在这里不是很清楚。 Resharper创建的代码发布在另一个答案中,在这种情况下很容易发生意外行为,因为Equals(object x)
和Equals(SecurableResourcePermission x)
会以不同的方式对待。
为了改变这种行为,必须在上面的强类型的"等于"方法中插入一个添加的类型检查:
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); }
下面,我总结了实现IEquatable时需要做的事情,并提供了各种MSDN文档页面中的论据。
概括
- 当需要测试值相等性时(例如,在集合中使用对象时),应实现IEquatable接口,为类覆盖Object.Equals和GetHashCode。
- 当需要测试引用相等性时,应使用operator ==,operator!=和Object.ReferenceEquals。
- 对于ValueType和不可变的引用类型,只应覆盖operator ==和operator!=。
理由
quaqua
The System.IEquatable interface is used to compare two instances of an object for equality. The objects are compared based on the logic implemented in the class. The comparison results in a boolean value indicating if the objects are different. This is in contrast to the System.IComparable interface, which return an integer indicating how the object values are different. The IEquatable interface declares two methods that must be overridden. The Equals method contains the implementation to perform the actual comparison and return true if the object values are equal, or false if they are not. The GetHashCode method should return a unique hash value that may be used to uniquely identify identical objects that contain different values. The type of hashing algorithm used is implementation-specific.
IEquatable.Equals方法
You should implement IEquatable for your objects to handle the possibility that they will be stored in an array or generic collection. If you implement IEquatable you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behavior is consistent with that of the IEquatable.Equals method
覆盖Equals()和运算符==的准则(C编程指南)
x.Equals(x) returns true. x.Equals(y) returns the same value as y.Equals(x) if (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true. Successive invocations of x. Equals (y) return the same value as long as the objects referenced by x and y are not modified. x. Equals (null) returns false (for non-nullable value types only. For more information, see Nullable Types (C# Programming Guide).) The new implementation of Equals should not throw exceptions. It is recommended that any class that overrides Equals also override Object.GetHashCode. Is is recommended that in addition to implementing Equals(object), any class also implement Equals(type) for their own type, to enhance performance. By default, the operator == tests for reference equality by determining whether two references indicate the same object. Therefore, reference types do not have to implement operator == in order to gain this functionality. When a type is immutable, that is, the data that is contained in the instance cannot be changed, overloading operator == to compare value equality instead of reference equality can be useful because, as immutable objects, they can be considered the same as long as they have the same value. It is not a good idea to override operator == in non-immutable types. Overloaded operator == implementations should not throw exceptions. Any type that overloads operator == should also overload operator !=.
==运算子(CReference)
For predefined value types, the equality operator (==) returns true if the values of its operands are equal, false otherwise. For reference types other than string, == returns true if its two operands refer to the same object. For the string type, == compares the values of the strings. When testing for null using == comparisons within your operator== overrides, make sure you use the base object class operator. If you don't, infinite recursion will occur resulting in a stackoverflow.
Object.Equals方法(对象)
If your programming language supports operator overloading and if you choose to overload the equality operator for a given type, that type must override the Equals method. Such implementations of the Equals method must return the same results as the equality operator The following guidelines are for implementing a value type: Consider overriding Equals to gain increased performance over that provided by the default implementation of Equals on ValueType. If you override Equals and the language supports operator overloading, you must overload the equality operator for your value type. The following guidelines are for implementing a reference type: Consider overriding Equals on a reference type if the semantics of the type are based on the fact that the type represents some value(s). Most reference types must not overload the equality operator, even if they override Equals. However, if you are implementing a reference type that is intended to have value semantics, such as a complex number type, you must override the equality operator.
其他陷阱
- 覆盖GetHashCode()时,请确保在哈希码中使用引用类型之前先对它们进行测试。
- 我遇到了此处描述的基于接口的编程和运算符重载的问题:C#中基于接口的编程的运算符重载