Tuple.Equals不检查确切类型的任何好理由
本文关键字:任何好 理由 类型 Equals 不检查 Tuple | 更新日期: 2023-09-27 18:33:09
检查类型失败会导致非对称相等:
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
这是因为元组不检查具体类型匹配,即没有等效的GetType() == obj.GetType()
。它通过is
检查检查可分配性,但不比较类型。
MyClass
我无能为力来解决这种情况,因为不正确的行Assert.AreEqual(a, b);
这是对Tuple.Equals
的调用。而且,正如juharr指出的那样,在这种情况下将MyClass.Equals
更改为返回true会破坏传递性。
长镜头,但我想知道是否有人知道为什么以这种方式实施它的充分理由?或者如果以这种方式实施,为什么它没有被密封。
不,我以前看过这个,据我所知,他们没有充分的理由不正确检查类型(除了他们从一开始就弄错了,然后当然不可能改变它)。
每个 MSDN 关于平等最佳实践的建议都谈到了执行"GetType() != obj。GetType()"以确保类型完全相同,但元组中的 equals 只使用"as"运算符进行强制转换,这将(如您所注意到的)给出意外的结果并禁用派生类遵守等于最佳实践的能力。
恕我直言 - 不要从元组派生,如果你这样做,绝对不要实现等于。
我已经实现了一个检查类型的替代方案,就我而言,它正确实现了相等协定:
https://mercurynuget.github.io/SuperTuples/
若要创建具有 GetHashcode
和Equals
实现(以及也像 Tuple 这样的ToString
)的类,可以选择缓存哈希。
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
)时,它执行了8倍,哈希缓存最大限度地减少了装箱和其他Equals
调用,因为Equals
首先比较缓存的哈希。
在 nuget 上可用:
安装包超级元组
作为它如何工作的一个例子,这是 tripple suple 的代码:
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;
}
}
}
组类的源代码(第 100 行)http://referencesource.microsoft.com/#mscorlib/system/tuple.cs,2e0df2b1d6d668a0
操作员的评估顺序从左到右