Tuple.Equals没有检查确切类型的任何好理由?

时间:2016-02-04 12:13:34

标签: c#

未检查类型可导致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将会破坏传递性。

远射,但我想知道是否有人知道为什么它以这种方式实施?或者,如果以这种方式实施,为什么它没有被密封。

3 个答案:

答案 0 :(得分:2)

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

每个MSDN关于equals最佳实践的建议都谈到了做“GetType()!= obj.GetType()”以确保类型完全相同,但在Tuple中等于只做一个使用'as'运算符强制转换(如您所见)会产生意外结果并禁用派生类遵循equals最佳实践的能力。

恕我直言 - 不要从元组派生出来,如果你这样做,肯定不会实现平等。

答案 1 :(得分:1)

我已经实施了一种检查类型的替代方案,并且只要我关注正确实现平等合同:

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

要创建一个GetHashcodeEquals实现的类(加上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 EqualsSupleHash.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

运营商的评估顺序是从左到右