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会破坏传递性。

长镜头,但我想知道是否有人知道为什么以这种方式实施它的充分理由?或者如果以这种方式实施,为什么它没有被密封。

Tuple.Equals不检查确切类型的任何好理由

不,我以前看过这个,据我所知,他们没有充分的理由不正确检查类型(除了他们从一开始就弄错了,然后当然不可能改变它)。

每个 MSDN 关于平等最佳实践的建议都谈到了执行"GetType() != obj。GetType()"以确保类型完全相同,但元组中的 equals 只使用"as"运算符进行强制转换,这将(如您所注意到的)给出意外的结果并禁用派生类遵守等于最佳实践的能力。

恕我直言 - 不要从元组派生,如果你这样做,绝对不要实现等于。

我已经实现了一个检查类型的替代方案,就我而言,它正确实现了相等协定:

https://mercurynuget.github.io/SuperTuples/

若要创建具有 GetHashcodeEquals实现(以及也像 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期间做更多的装箱,但实际上它执行TupleEquals,当哈希被缓存(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

操作员的评估顺序从左到右