用于Java Map的.NET Dictionary / IDictionary与equals()契约的Equals()契约

时间:2010-10-12 21:56:02

标签: java .net collections immutability equality

怀念Collections.unmodifiableMap(),我一直在实施基于this discussion的只读IDictionary包装器,我的单元测试很快遇到了问题:

Assert.AreEqual (backingDictionary, readOnlyDictionary);
即使键值对匹配,

也会失败。我玩了一下,看起来至少(谢谢西蒙尼)

Assert.AreEquals (backingDictionary, new Dictionary<..> { /* same contents */ });

确实通过了。

我快速查看了DictionaryIDictionary文档,令我惊讶的是,我找不到任何与Maps合同相同的entrySet()s合同。Dictionary等于IDictionary必须相等。 (文档说Equals() - Dictionary - 覆盖IDictionary,但不要说覆盖的内容。)

因此看起来C#中的键值相等是System.Collections具体类的属性,而不是ReadOnlyDictionary接口的属性。这是正确的吗?整个IDictionary框架通常是否正确?

如果是这样的话,我有兴趣阅读一些关于为什么MS选择这种方法的讨论 - 以及在C#中检查收集内容是否相等的首选方法。

最后,我不介意指向经过充分测试的Map实现的指针。 :)


ETA:要明确,我正在寻找有关如何测试我的实施的建议 - 这是相对微不足道的。我正在寻找关于这些测试应该执行什么合同的指导。以及为什么。


ETA :我知道 equals() {{1}}是一个接口,我知道接口无法实现方法。在Java中也是如此。然而,Java {{1}}接口记录了{{1}}方法对Map的期望。当然必须有.NET接口执行这样的操作,即使集合接口不在其中。

6 个答案:

答案 0 :(得分:3)

覆盖等于通常仅对具有一定程度的值语义的类(例如string)进行。引用相等是人们更常关注的大多数引用类型和良好的默认值,特别是在可能不太明确的情况下(两个字典具有完全相同的键值对但不同的相等比较器[并因此添加相同的额外键值对可以使它们现在变得相同或不相同?)或者不经常寻找值相等的地方。

毕竟,您正在寻找两种不同类型被认为相同的情况。相等覆盖可能仍会使您失败。

更是如此,因为你总能足够快地创建自己的相等比较器:

public class SimpleDictEqualityComparer<TKey, TValue> : IEqualityComparer<IDictionary<TKey, TValue>>
{
    // We can do a better job if we use a more precise type than IDictionary and use
    // the comparer of the dictionary too.
    public bool Equals(IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y)
    {
        if(ReferenceEquals(x, y))
            return true;
        if(ReferenceEquals(x, null) || ReferenceEquals(y, null))
            return false;
        if(x.Count != y.Count)
            return false;
        TValue testVal = default(TValue);
        foreach(TKey key in x.Keys)
            if(!y.TryGetValue(key, out testVal) || !Equals(testVal, x[key]))
                return false;
        return true;
    }
    public int GetHashCode(IDictionary<TKey, TValue> dict)
    {
        unchecked
        {
            int hash = 0x15051505;
            foreach(TKey key in dict.Keys)
            {
                var value = dict[key];
                var valueHash = value == null ? 0 : value.GetHashCode();
                hash ^= ((key.GetHashCode() << 16 | key.GetHashCode() >> 16) ^ valueHash);
            }
            return hash;
        }
    }
}

这不会为所有可能需要比较字典的情况提供服务,但那时,这就是我的观点。

用“可能是他们的意思”平等方法填补BCL将是一件令人讨厌的事情,而不是一种帮助。

答案 1 :(得分:2)

我建议使用NUnit的CollectionAssert.AreEquivalent()。 Assert.AreEqual()实际上不适用于集合。 http://www.nunit.org/index.php?p=collectionAssert&r=2.4

答案 2 :(得分:2)

对于后来的读者,这是我被告知/能够弄清楚的:

  1. .NET集合的合同, 与Java集合不同,不是 包括任何特定的行为 Equals()GetHashCode()
  2. LINQ Enumerable.SequenceEqual() 扩展方法将起作用 有序集合,包括 词典 - 表现为 IEnumerable<KeyValuePair>; KeyValuePair是一个结构,它的结构 Equals方法uses reflection 比较内容。
  3. Enumerable提供其他扩展程序 可以用来拼凑的方法 一起进行内容平等检查 作为Union()Intersect()
  4. 我正在接受这样的想法,就像Java方法一样,如果我们讨论可变集合,以及典型的隐式equals()语义,那么它们可能不是最好的主意。两个equal对象是可互换的。 .NET不能为不可变集合提供非常好的支持,但开源PowerCollections库可以。

答案 3 :(得分:1)

public sealed class DictionaryComparer<TKey, TValue>
    : EqualityComparer<IDictionary<TKey, TValue>>
{
    public override bool Equals(
        IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y)
    {
        if (object.ReferenceEquals(x, y)) return true;
        if ((x == null) || (y == null)) return false;
        if (x.Count != y.Count) return false;

        foreach (KeyValuePair<TKey, TValue> kvp in x)
        {
            TValue yValue;
            if (!y.TryGetValue(kvp.Key, out yValue)) return false;
            if (!kvp.Value.Equals(yValue)) return false;
        }
        return true;
    }

    public override int GetHashCode(IDictionary<TKey, TValue> obj)
    {
        unchecked
        {
            int hash = 1299763;
            foreach (KeyValuePair<TKey, TValue> kvp in obj)
            {
                int keyHash = kvp.Key.GetHashCode();
                if (keyHash == 0) keyHash = 937;

                int valueHash = kvp.Value.GetHashCode();
                if (valueHash == 0) valueHash = 318907;

                hash += (keyHash * valueHash);
            }
            return hash;
        }
    }
}

答案 4 :(得分:1)

  

所以它看起来像键值相等   C#是Dictionary的属性   具体类,而不是IDictionary   接口。这是正确的吗?是吗   一般来说都是如此   System.Collections框架?

     

如果是这样,我有兴趣阅读一些内容   讨论为什么MS选择那个   方法

我认为这很简单 - IDictionary是一个接口,接口不能有任何实现,在.NET世界中,两个对象的相等是通过Equals方法定义的。因此,只允许覆盖IDictionary接口的Equals以允许它拥有“键值相等”。

答案 5 :(得分:0)

你在原帖中犯了大错。您在Equals()界面中谈到了IDictionary方法。这就是重点!

Equals()是类可以覆盖的System.Object的虚方法。接口根本不实现方法。相反,接口的实例是引用类型,因此继承自System.Object可能声明覆盖Equals()

现在要点...... System.Collections.Generic.Dictionary<K,V> 重写等于。你说你用自己的方式实现了你的IDictionary,并合理地覆盖了Equals,但是看看你自己的代码

Assert.AreEqual (backingDictionary, readOnlyDictionary); 

此方法基本上实现为return backingDictionary.Equals(readOnlyDictionary),这里也是重点。

如果两个对象是不同类的实例,则基本Equals()方法返回false,您无法控制它。否则,如果两个对象属于同一类型,则使用Equals()方法而不是==方法通过反射(只是成员,而不是属性)比较每个成员(这是手动调用的“值比较”) “而不是”参考比较“)

首先,如果Assert.AreEqual (readOnlyDictionary,backingDictionary);成功,我不会感到惊讶,因为它会触发用户定义的Equals方法。

我毫不怀疑此线程中其他用户的方法是有效的,但我只是想解释一下原始方法中的错误是什么。当然,微软最好实现一个Equals方法,将当前实例与任何其他IDictionary实例进行比较,但同样,这将超出Dictionary类的范围,这是一个公共独立类,并不意味着IDictionary唯一公开可用的实现。例如,当您定义一个接口,一个工厂和一个在库中实现它的受保护类时,您可能希望将该类与基本接口的其他实例进行比较,而不是与非公开的类本身进行比较。

我希望对你有所帮助。 欢呼声。