未检查类型可导致un-symmetric相等:
public sealed class MyClass : Tuple<string>
{
private readonly int _b;
public MyClass(string a, int b) : base(a)
{
_b = b;
}
public override bool Equals(object obj)
{
return Equals(obj as MyClass);
}
private bool Equals(MyClass obj)
{
if (obj == null) return false;
return base.Equals(obj) && obj._b == _b;
}
}
[Test]
public void Show_broken_symmetric_equality()
{
Tuple<string> a = Tuple.Create("Test");
var b = new MyClass("Test", 3);
Assert.AreEqual(a, b);
Assert.AreNotEqual(b, a);
}
该测试通过,但不应该,它表明实现良好的Equals
的对称属性已被破坏。
查看Tuple
的代码,因为Tuple没有检查具体的类型匹配,即没有等效的GetType() == obj.GetType()
。它通过is
检查检查可分配性,但不比较类型。
我无法在MyClass
中解决此问题,因为错误的行是Assert.AreEqual(a, b);
,这是对Tuple.Equals
的调用。并且,正如juharr指出的那样,在这种情况下将MyClass.Equals
更改为true将会破坏传递性。
远射,但我想知道是否有人知道为什么它以这种方式实施?或者,如果以这种方式实施,为什么它没有被密封。
答案 0 :(得分:2)
不,我之前已经看过这个,据我所知,没有充分的理由说明他们没有正确检查类型(除非他们从一开始就弄错了然后它当然不可能改变它)。
每个MSDN关于equals最佳实践的建议都谈到了做“GetType()!= obj.GetType()”以确保类型完全相同,但在Tuple中等于只做一个使用'as'运算符强制转换(如您所见)会产生意外结果并禁用派生类遵循equals最佳实践的能力。
恕我直言 - 不要从元组派生出来,如果你这样做,肯定不会实现平等。
答案 1 :(得分:1)
我已经实施了一种检查类型的替代方案,并且只要我关注正确实现平等合同:
https://mercurynuget.github.io/SuperTuples/
要创建一个GetHashcode
和Equals
实现的类(加上ToString
也像Tuple一样),可以选择缓存哈希值。
public class Person : Suple<string, string>
{
public Person(string firstName, string lastName)
: base(firstName, lastName, SupleHash.Cached)
{
}
public string FirstName => Item1;
public string LastName => Item2;
}
它的实现比Tuple
更简单,因此可以在Equals
期间进行更多拳击,但实际上它会执行Tuple
Equals
和SupleHash.Cached
当缓存(Equals
)和散列缓存最小化装箱和其他Equals
调用时,最多8次,因为public abstract class Suple<T1, T2, T3>
{
private readonly T1 _item1;
private readonly T2 _item2;
private readonly T3 _item3;
private readonly int? _cachedHash;
protected Suple(T1 item1, T2 item2, T3 item3)
{
_item1 = item1;
_item2 = item2;
_item3 = item3;
}
protected Suple(T1 item1, T2 item2, T3 item3, SupleHash hashMode)
{
_item1 = item1;
_item2 = item2;
_item3 = item3;
_cachedHash = CalculateHashCode();
}
protected T1 Item1 { get { return _item1; } }
protected T2 Item2 { get { return _item2; } }
protected T3 Item3 { get { return _item3; } }
public override bool Equals(object obj)
{
if (obj == null) return false;
// here's the missing Tuple type comparison
if (GetType() != obj.GetType()) return false;
var other = (Suple<T1, T2, T3>) obj;
// attempt to avoid equals comparison by using the
// cached hash if provided by both objects
if (_cachedHash != null &&
other._cachedHash != null &&
_cachedHash != other._cachedHash) return false;
return Equals(_item1, other._item1) &&
Equals(_item2, other._item2) &&
Equals(_item3, other._item3);
}
public override int GetHashCode()
{
return _cachedHash ?? CalculateHashCode();
}
private int CalculateHashCode()
{
unchecked
{
int hashcode = 0;
hashcode += _item1 != null ? _item1.GetHashCode() : 0;
hashcode *= 31;
hashcode += _item2 != null ? _item2.GetHashCode() : 0;
hashcode *= 31;
hashcode += _item3 != null ? _item3.GetHashCode() : 0;
return hashcode;
}
}
}
首先比较缓存的散列。
可在nuget上使用:
Install-Package SuperTuples
作为它如何工作的一个例子,这是tripple suple的代码:
std::istringstream myStream{"This is expected content"};
mocks::MyMockClass mockClass{myStream};
答案 2 :(得分:-4)
参见元组类的来源(第100行) http://referencesource.microsoft.com/#mscorlib/system/tuple.cs,2e0df2b1d6d668a0
运营商的评估顺序是从左到右