我正在使用域模型,并且正在考虑在.NET中实现这两种方法的各种方法。你最喜欢的策略是什么?
这是我目前的实施:
public override bool Equals(object obj)
{
var newObj = obj as MyClass;
if (null != newObj)
{
return this.GetHashCode() == newObj.GetHashCode();
}
else
{
return base.Equals(obj);
}
}
// Since this is an entity I can use its Id
// When I don't have an Id, I usually make a composite key of the properties
public override int GetHashCode()
{
return String.Format("MyClass{0}", this.Id.ToString()).GetHashCode();
}
答案 0 :(得分:31)
Domain-Driven Design区分实体和值对象。这是一个很好的区分,因为它指导你如何实现Equals。
实体如果ID彼此相等则相等。
值对象如果所有(重要)组成元素彼此相等则相等。
在任何情况下,GetHashCode的实现都应基于用于确定相等性的相同值。换句话说,对于实体,哈希码应该直接从ID计算,而对于值对象,它应该从所有组成值计算。
答案 1 :(得分:5)
这里没有任何答案真的让我感到满意。既然您已经说过不能使用Id
进行相等性,并且需要使用一组属性,那么这是一种更好的方法。注意:我不认为这总体上是实施Equals
和GetHashCode
的最佳方式。这是OP代码的更好版本。
public override bool Equals(object obj) {
var myClass = obj as MyClass;
if (myClass != null) {
// Order these by the most different first.
// That is, whatever value is most selective, and the fewest
// instances have the same value, put that first.
return this.Id == myClass.Id
&& this.Name == myClass.Name
&& this.Quantity == myClass.Quantity
&& this.Color == myClass.Color;
} else {
// This may not make sense unless GetHashCode refers to `base` as well!
return base.Equals(obj);
}
}
public override int GetHashCode() {
int hash = 19;
unchecked { // allow "wrap around" in the int
hash = hash * 31 + this.Id; // assuming integer
hash = hash * 31 + this.Name.GetHashCode();
hash = hash * 31 + this.Quantity; // again assuming integer
hash = hash * 31 + this.Color.GetHashCode();
}
return hash;
}
有关此背后的一些原因,请参阅this answer by Jon Skeet。使用xor并不好,因为各种数据集最终会导致相同的散列。这种带素数的环绕方法(上面的种子值为19和31,或者你选择的其他值)可以更好地分割成每个碰撞很少的“桶”。
如果您的任何值可以为null,我建议您仔细考虑它们应如何比较。您可以使用短路空值评估和空合并运算符。但是请确保如果null应该相等,那么当它们为null时,将不同的哈希代码分配给不同的可空属性。
另外,我不相信你的Equals
实现有任何意义。比较两个对象的相等性时,首先比较它们的GetHashCode
值。只有那些不同的是Equals
方法运行(因此,如果散列到相同值的两个对象不同,则会检测到这个)。由于您的GetHashCode
实施没有引用base
,因此您的Equals
方法可能没有任何意义。具体来说,如果Equals
对于哈希码不同的两个对象都可以返回true,那么你将有一个严重的错误等待破解。
答案 2 :(得分:2)
Hashcodes可能会发生碰撞,所以我认为它们不是比较平等的好方法。您应该比较使对象“相等”的基础值。如果您的相等性包含多个属性,请参阅@Jon Skeet对此问题的回答:What is the best algorithm for an overridden System.Object.GetHashCode?以获得更好的GetHashCode实现。如果它只是一个属性,你可以只重用它的哈希码。
答案 3 :(得分:1)
假设实例相等,因为哈希码相等是错误的。
我猜你的GetHashCode实现没问题,但我通常使用类似的东西:
public override int GetHashCode() {
return object1.GetHashCode ^ intValue1 ^ (intValue2 << 16);
}
答案 4 :(得分:1)
我偶然发现了这个老问题,恕我直言,我没有找到任何答案清楚简单地说明@tucaz提出的原始问题。
我同意上面(或下面:D)分享的许多考虑因素,但遗漏了“问题点”(我认为)。
提供:
我猜可以直接实现一个:
public class MyEntity: IEquatable<MyEntity> {
int Id;
public MyEntity(int id){
Id = id;
}
public override bool Equals(object obj) => Equals(obj as MyEntity);
public bool Equals(MyEntity obj) => obj != null && Id == obj.Id;
public override int GetHashCode() => Id;
}
这就是全部!
答案 5 :(得分:1)
除了答案(不允许写评论)外,我想指出Visual Studio可以自动生成Equals和GetHashCode。 查看此答案:Is there a way to automatically generate equals and hashcode method in Visual Studio 我真的在寻找该自定义实现,而在这里找不到。
我也想链接这个问题: Best way to compare two complex objects 它与嵌套类结构有关。在注释的下方,可以找到带有Enumerations(例如List)的嵌套类结构的情况。
答案 6 :(得分:0)
我想根据上面的答案和我自己的经验来看一些特定的情况。
经验法则是,具有不同哈希码的两个实例应始终不相等,但如果哈希值相同,则它们可能相等也可能不相等。GetHashCode()
用于快速区分实例,Equals()
用于验证相等性(对您而言意味着什么)。
还有许多内置机制正在寻找IEquatable<T>
的实现,因此,最好声明实际上执行检查的Equals(MyClass)
的替代。
考虑一个具有唯一ID的类。然后equals操作将只检查ID。哈希也是如此,仅依赖于ID。
public class IdClass : IEquatable<IdClass>
{
public int ID { get; } // Assume unique
public string Name { get; }
#region IEquatable Members
/// <summary>
/// Equality overrides from <see cref="System.Object"/>
/// </summary>
/// <param name="obj">The object to compare this with</param>
/// <returns>False if object is a different type, otherwise it calls <code>Equals(IdClass)</code></returns>
public override bool Equals(object obj)
{
if (obj is IdClass other)
{
return Equals(other);
}
return false;
}
/// <summary>
/// Checks for equality among <see cref="IdClass"/> classes
/// </summary>
/// <param name="other">The other <see cref="IdClass"/> to compare it to</param>
/// <returns>True if equal</returns>
public virtual bool Equals(IdClass other)
{
if (other == null) return false;
return ID.Equals(other.ID);
}
/// <summary>
/// Calculates the hash code for the <see cref="IdClass"/>
/// </summary>
/// <returns>The int hash value</returns>
public override int GetHashCode() => ID.GetHashCode();
#endregion
}
这种情况与上述类似,但是比较取决于两个或多个属性,并且需要在哈希码中进行非对称组合。在下一个场景中,这一点将变得更加明显,但是我们的想法是,如果一个属性具有哈希A
,而另一个属性具有哈希B
,则结果应该与第一个属性具有哈希{{ }}和另一个哈希B
。
A
这几乎与上面的情况相同,除了作为值类型(public class RefClass : IEquatable<RefClass>
{
public string Name { get; }
public int Age { get; }
#region IEquatable Members
/// <summary>
/// Equality overrides from <see cref="System.Object"/>
/// </summary>
/// <param name="obj">The object to compare this with</param>
/// <returns>False if object is a different type, otherwise it calls <code>Equals(RefClass)</code></returns>
public override bool Equals(object obj)
{
if (obj is RefClass other)
{
return Equals(other);
}
return false;
}
/// <summary>
/// Checks for equality among <see cref="RefClass"/> classes
/// </summary>
/// <param name="other">The other <see cref="RefClass"/> to compare it to</param>
/// <returns>True if equal</returns>
public virtual bool Equals(RefClass other)
{
if (other == null) { return false; }
return Name.Equals(other.Name)
&& Age.Equals(other.Age);
}
/// <summary>
/// Calculates the hash code for the <see cref="RefClass"/>
/// </summary>
/// <returns>The int hash value</returns>
public override int GetHashCode()
{
unchecked
{
int hc = -1817952719;
hc = (-1521134295) * hc + Name.GetHashCode();
hc = (-1521134295) * hc + Age.GetHashCode();
return hc;
}
}
#endregion
}
声明)还需要重新定义struct
和==
才能调用equals。
!=
请注意,public struct ValClass : IEquatable<ValClass>
{
public int X { get; }
public int Y { get; }
#region IEquatable Members
/// <summary>
/// Equality overrides from <see cref="System.Object"/>
/// </summary>
/// <param name="obj">The object to compare this with</param>
/// <returns>False if object is a different type, otherwise it calls <code>Equals(ValClass)</code></returns>
public override bool Equals(object obj)
{
if (obj is ValClass other)
{
return Equals(other);
}
return false;
}
public static bool operator ==(ValClass target, ValClass other) { return target.Equals(other); }
public static bool operator !=(ValClass target, ValClass other) { return !(target == other); }
/// <summary>
/// Checks for equality among <see cref="ValClass"/> classes
/// </summary>
/// <param name="other">The other <see cref="ValClass"/> to compare it to</param>
/// <returns>True if equal</returns>
public bool Equals(ValClass other)
{
return X == other.X && Y == other.Y;
}
/// <summary>
/// Calculates the hash code for the <see cref="ValClass"/>
/// </summary>
/// <returns>The int hash value</returns>
public override int GetHashCode()
{
unchecked
{
int hc = -1817952719;
hc = (-1521134295) * hc + X.GetHashCode();
hc = (-1521134295) * hc + Y.GetHashCode();
return hc;
}
}
#endregion
}
应该是不可变的,在声明中添加struct
关键字是一个好主意
readonly