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

问题描述:

未检查类型可以导致un-symmetric平等:Tuple.Equals没有检查确切类型的任何好理由?

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将会中断传递性。

远射,但我想知道有没有人知道为什么它以这种方式实现的一个很好的理由?或者为什么如果以这种方式实施它,则不是密封的。

+1

这是不关于Tuple的唯一奇怪的事情。实施是可怕的,它是缓慢和笨拙的。我的建议是根本不使用Tuple ... – Roland

+1

我想检查类型检查类型是否与检查MyClass相同 - 但是您可以将MyClass的实例转换为Tuple 然后比较会员。因此这个检查完全有效。 – HimBromBeere

+3

但[[Tuple .Equals'](https://msdn.microsoft.com/en-us/library/dd270346(v = vs.110).aspx)检查类型。 _“在以下条件下obj参数被认为等于当前实例: **它是一个元组对象** 它的两个组件与当前实例的类型相同 它的两个组件是等同于当前实例的相等性。相等由每个组件的默认对象相等比较器确定。“# –

见元组类(行100) http://referencesource.microsoft.com/#mscorlib/system/tuple.cs,2e0df2b1d6d668a0

评价为运营商的订单来源左至右

没有,我在此之前看了一下,据我了解,没有为什么他们没有正确地检查类型(除了他们从一开始就错了,然后当然不可能改变它)。

最佳实践每个MSDN忠告约等于做讲座“的GetType()!= obj.GetType()”,以确保该类型是正是相同,但在元组等于只做与“作为投'这个操作符将会(如你所注意到的)给出意想不到的结果,并且禁止派生类遵循equals的最佳实践。

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

+0

当然,我也得出了相同的结论,有些建议不能改变'GetHashCode'的行为。 – weston

,我实现了做检查类型和就我而言,正确实现平等契约替代:

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做更多的拳,但实际上它执行TupleEquals和多达8倍时,哈希缓存(SupleHash.Cached)和散列缓存最小化拳击和其他Equals调用,因为Equals首先比较缓存的散列。

上可用的NuGet:

安装,包装SuperTuples

至于它是如何工作的例子,这是对特里普尔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; 
     } 
    } 
} 
+0

我打算测验你“减少拳击”的部分,然后我看看'Tuple '的来源,实际上它使用'EqualityComparer .Default'来比较项目,导致值类型的装箱。 –

+0

@KirillShlenskiy我在说我的实现与哈希缓存启用,最大限度地减少拳击相比,我的实施哈希缓存关闭。我一直认为Tuple代码实际上非常复杂以避免装箱,例如:http://referencesource.microsoft。com /#mscorlib/system/collections/generic/equalitycomparer.cs,542680fa5b2d828d但是你说得对,那只是一个'EqualityComparer .Default'所以我不知道他们要做什么。 – weston