当 T 是接口并且哈希码在派生类型之间不同时覆盖 IEquatable<T>
Overriding IEquatable<T> when T is an interface and hashcodes are different between derived types
我有 A
和 B
class 都实现了接口 I
。
public interface I
{
int SomeInt { get; }
bool SomeBool { get; }
float SomeFloat { get; }
}
public class A : I
{
public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }
private readonly string _someARelatedStuff;
// Rest of class...
}
public class B : I
{
public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }
private string readonly _someBRelatedStuff;
private double readonly _someOtherBRelatedStuff;
// Rest of class...
}
有时我想测试 A
和 B
之间的相等性(通常在比较 A
列表和 B
列表时)基于它们的 I
属性(SomeInt
、SomeBool
、SomeFloat
),所以我在两者上实现了 IEquatable<I>
,并根据它们共享的 I
属性值对它们进行了比较.
问题是我已经在 A 和 B 上为 GetHashCode()
实现了生成不同哈希值的实现,因为我正在考虑其他成员。
B
不依赖于 A
所以我使用接口 I
来比较它们,它有一个带有吸气剂的属性列表。
我读 in a Whosebug answer 是:
If you are implementing a class, you should always make sure that two equal objects have the same hashcode.
这是否意味着每次 class A
想要实现接口 I
,并且我希望能够比较实现 I
的实例,我必须确保对 I
的所有实例以相同的方式计算哈希码并且仅使用 I
属性?
当 T 是一个接口时,我确实觉得我不打算实现 IEquatable<T>
,但我的替代方案是:
- 使用带有基础的常规继承 class - 我宁愿尽可能避免继承,如果 B 需要从某个框架派生,这个解决方案将不起作用
C
class 因为单一继承
- 使用
A
或 B
上的方法在 A
和 B
之间实施相等性检查 - 将创建代码重复
- 在
I
中定义的 I
个实例之间有一个相等性检查方法 - 听起来是最好的选择
有没有我遗漏的选项?
考虑制作 IEqualityComparer<>
class 来比较通用值。
为了便于阅读,我已将界面重命名为 ICommon
public interface ICommon
{
int SomeInt { get; }
bool SomeBool { get; }
float SomeFloat { get; }
}
public class CommonComparer : IEqualityComparer<ICommon>
{
public bool Equals(ICommon x, ICommon y)
{
return x.SomeInt.Equals(y.SomeInt)
&& x.SomeBool.Equals(y.SomeBool)
&& x.SomeFloat.Equals(y.SomeFloat);
}
public int GetHashCode(ICommon obj)
{
unchecked
{
int hc = -1817952719;
hc = (-1521134295)*hc + obj.SomeInt.GetHashCode();
hc = (-1521134295)*hc + obj.SomeBool.GetHashCode();
hc = (-1521134295)*hc + obj.SomeFloat.GetHashCode();
return hc;
}
}
}
并且程序可以区分两个列表中的相同项。
class Program
{
static void Main(string[] args)
{
var listA = new List<A>
{
new A(1001, true, 1.001f, "A1"),
new A(1002, true, 1.002f, "A2"),
new A(1003, false, 1.003f, "A1"),
new A(1004, false, 1.004f, "A4")
};
var listB = new List<B>
{
new B(1001, true, 1.001f, "B1", 2.5),
new B(1002, false, 1.002f, "B2", 2.8),
new B(1003, true, 1.003f, "B3", 2.9),
new B(1004, false, 1.004f, "B4", 2.9)
};
var common = Enumerable.Intersect(listA, listB, new CommonComparer()).OfType<ICommon>();
Console.WriteLine($"{"SomeInt",-8} {"Bool",-6} {"SomeFloat",-10}");
foreach (var item in common)
{
Console.WriteLine($"{item.SomeInt,-8} {item.SomeBool,-6} {item.SomeFloat,-10}");
}
//SomeInt Bool SomeFloat
//1001 True 1.001
//1004 False 1.004
}
}
和其余代码定义
public class A : ICommon, IEquatable<A>
{
static readonly CommonComparer comparer = new CommonComparer();
public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }
private readonly string _someARelatedStuff;
// Rest of class...
public A(ICommon other, string someARelatedStuff)
: this(other.SomeInt, other.SomeBool, other.SomeFloat, someARelatedStuff)
{ }
public A(int someInt, bool someBool, float someFloat, string someARelatedStuff)
{
this.SomeInt = someInt;
this.SomeBool = someBool;
this.SomeFloat = someFloat;
this._someARelatedStuff = someARelatedStuff;
}
public override string ToString() => _someARelatedStuff;
#region IEquatable Members
public override bool Equals(object obj)
{
if (obj is A other)
{
return Equals(other);
}
return false;
}
public virtual bool Equals(A other)
{
return comparer.Equals(this, other)
&& _someARelatedStuff.Equals(other._someARelatedStuff);
}
public override int GetHashCode()
{
unchecked
{
int hc = comparer.GetHashCode(this);
hc = (-1521134295)*hc + _someARelatedStuff.GetHashCode();
return hc;
}
}
#endregion
}
public class B : ICommon, IEquatable<B>
{
static readonly CommonComparer comparer = new CommonComparer();
public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }
readonly string _someBRelatedStuff;
readonly double _someOtherBRelatedStuff;
// Rest of class...
public B(ICommon other, string someBRelatedStuff, double someOtherBRelatedStuff)
: this(other.SomeInt, other.SomeBool, other.SomeFloat, someBRelatedStuff, someOtherBRelatedStuff)
{ }
public B(int someInt, bool someBool, float someFloat, string someBRelatedStuff, double someOtherBRelatedStuff)
{
this.SomeInt = someInt;
this.SomeBool = someBool;
this.SomeFloat = someFloat;
this._someBRelatedStuff = someBRelatedStuff;
this._someOtherBRelatedStuff = someOtherBRelatedStuff;
}
public override string ToString() => $"{_someBRelatedStuff}, {_someOtherBRelatedStuff.ToString("g4")}";
#region IEquatable Members
public override bool Equals(object obj)
{
if (obj is B other)
{
return Equals(other);
}
return false;
}
public virtual bool Equals(B other)
{
return comparer.Equals(this, other)
&& _someBRelatedStuff.Equals(other._someBRelatedStuff)
&& _someOtherBRelatedStuff.Equals(other._someOtherBRelatedStuff);
}
public override int GetHashCode()
{
unchecked
{
int hc = comparer.GetHashCode(this);
hc = (-1521134295)*hc + _someBRelatedStuff.GetHashCode();
hc = (-1521134295)*hc + _someOtherBRelatedStuff.GetHashCode();
return hc;
}
}
#endregion
}
我有 A
和 B
class 都实现了接口 I
。
public interface I
{
int SomeInt { get; }
bool SomeBool { get; }
float SomeFloat { get; }
}
public class A : I
{
public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }
private readonly string _someARelatedStuff;
// Rest of class...
}
public class B : I
{
public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }
private string readonly _someBRelatedStuff;
private double readonly _someOtherBRelatedStuff;
// Rest of class...
}
有时我想测试 A
和 B
之间的相等性(通常在比较 A
列表和 B
列表时)基于它们的 I
属性(SomeInt
、SomeBool
、SomeFloat
),所以我在两者上实现了 IEquatable<I>
,并根据它们共享的 I
属性值对它们进行了比较.
问题是我已经在 A 和 B 上为 GetHashCode()
实现了生成不同哈希值的实现,因为我正在考虑其他成员。
B
不依赖于 A
所以我使用接口 I
来比较它们,它有一个带有吸气剂的属性列表。
我读 in a Whosebug answer 是:
If you are implementing a class, you should always make sure that two equal objects have the same hashcode.
这是否意味着每次 class A
想要实现接口 I
,并且我希望能够比较实现 I
的实例,我必须确保对 I
的所有实例以相同的方式计算哈希码并且仅使用 I
属性?
当 T 是一个接口时,我确实觉得我不打算实现 IEquatable<T>
,但我的替代方案是:
- 使用带有基础的常规继承 class - 我宁愿尽可能避免继承,如果 B 需要从某个框架派生,这个解决方案将不起作用
C
class 因为单一继承 - 使用
A
或B
上的方法在A
和B
之间实施相等性检查 - 将创建代码重复 - 在
I
中定义的I
个实例之间有一个相等性检查方法 - 听起来是最好的选择
有没有我遗漏的选项?
考虑制作 IEqualityComparer<>
class 来比较通用值。
为了便于阅读,我已将界面重命名为 ICommon
public interface ICommon
{
int SomeInt { get; }
bool SomeBool { get; }
float SomeFloat { get; }
}
public class CommonComparer : IEqualityComparer<ICommon>
{
public bool Equals(ICommon x, ICommon y)
{
return x.SomeInt.Equals(y.SomeInt)
&& x.SomeBool.Equals(y.SomeBool)
&& x.SomeFloat.Equals(y.SomeFloat);
}
public int GetHashCode(ICommon obj)
{
unchecked
{
int hc = -1817952719;
hc = (-1521134295)*hc + obj.SomeInt.GetHashCode();
hc = (-1521134295)*hc + obj.SomeBool.GetHashCode();
hc = (-1521134295)*hc + obj.SomeFloat.GetHashCode();
return hc;
}
}
}
并且程序可以区分两个列表中的相同项。
class Program
{
static void Main(string[] args)
{
var listA = new List<A>
{
new A(1001, true, 1.001f, "A1"),
new A(1002, true, 1.002f, "A2"),
new A(1003, false, 1.003f, "A1"),
new A(1004, false, 1.004f, "A4")
};
var listB = new List<B>
{
new B(1001, true, 1.001f, "B1", 2.5),
new B(1002, false, 1.002f, "B2", 2.8),
new B(1003, true, 1.003f, "B3", 2.9),
new B(1004, false, 1.004f, "B4", 2.9)
};
var common = Enumerable.Intersect(listA, listB, new CommonComparer()).OfType<ICommon>();
Console.WriteLine($"{"SomeInt",-8} {"Bool",-6} {"SomeFloat",-10}");
foreach (var item in common)
{
Console.WriteLine($"{item.SomeInt,-8} {item.SomeBool,-6} {item.SomeFloat,-10}");
}
//SomeInt Bool SomeFloat
//1001 True 1.001
//1004 False 1.004
}
}
和其余代码定义
public class A : ICommon, IEquatable<A>
{
static readonly CommonComparer comparer = new CommonComparer();
public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }
private readonly string _someARelatedStuff;
// Rest of class...
public A(ICommon other, string someARelatedStuff)
: this(other.SomeInt, other.SomeBool, other.SomeFloat, someARelatedStuff)
{ }
public A(int someInt, bool someBool, float someFloat, string someARelatedStuff)
{
this.SomeInt = someInt;
this.SomeBool = someBool;
this.SomeFloat = someFloat;
this._someARelatedStuff = someARelatedStuff;
}
public override string ToString() => _someARelatedStuff;
#region IEquatable Members
public override bool Equals(object obj)
{
if (obj is A other)
{
return Equals(other);
}
return false;
}
public virtual bool Equals(A other)
{
return comparer.Equals(this, other)
&& _someARelatedStuff.Equals(other._someARelatedStuff);
}
public override int GetHashCode()
{
unchecked
{
int hc = comparer.GetHashCode(this);
hc = (-1521134295)*hc + _someARelatedStuff.GetHashCode();
return hc;
}
}
#endregion
}
public class B : ICommon, IEquatable<B>
{
static readonly CommonComparer comparer = new CommonComparer();
public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }
readonly string _someBRelatedStuff;
readonly double _someOtherBRelatedStuff;
// Rest of class...
public B(ICommon other, string someBRelatedStuff, double someOtherBRelatedStuff)
: this(other.SomeInt, other.SomeBool, other.SomeFloat, someBRelatedStuff, someOtherBRelatedStuff)
{ }
public B(int someInt, bool someBool, float someFloat, string someBRelatedStuff, double someOtherBRelatedStuff)
{
this.SomeInt = someInt;
this.SomeBool = someBool;
this.SomeFloat = someFloat;
this._someBRelatedStuff = someBRelatedStuff;
this._someOtherBRelatedStuff = someOtherBRelatedStuff;
}
public override string ToString() => $"{_someBRelatedStuff}, {_someOtherBRelatedStuff.ToString("g4")}";
#region IEquatable Members
public override bool Equals(object obj)
{
if (obj is B other)
{
return Equals(other);
}
return false;
}
public virtual bool Equals(B other)
{
return comparer.Equals(this, other)
&& _someBRelatedStuff.Equals(other._someBRelatedStuff)
&& _someOtherBRelatedStuff.Equals(other._someOtherBRelatedStuff);
}
public override int GetHashCode()
{
unchecked
{
int hc = comparer.GetHashCode(this);
hc = (-1521134295)*hc + _someBRelatedStuff.GetHashCode();
hc = (-1521134295)*hc + _someOtherBRelatedStuff.GetHashCode();
return hc;
}
}
#endregion
}