简体   繁体   English

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

[英]Equals() contract for .NET Dictionary / IDictionary vs equals() contract for Java Map

Nostalgic for Collections.unmodifiableMap() , I've been implementing a read-only IDictionary wrapper based on this discussion , and my unit test quickly ran into a problem: 对于Collections.unmodifiableMap() Nostalgic,我一直在实现基于这个讨论的只读IDictionary包装器,我的单元测试很快遇到了一个问题:

Assert.AreEqual (backingDictionary, readOnlyDictionary);

fails, even though the key-value pairs match. 即使键值对匹配,也会失败。 I played around a little more, and it looks like at least (thank Simonyi) 我玩了一下,看起来至少(感谢Simonyi)

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

does pass. 确实过去了。

I took a quick look through the Dictionary and IDictionary documentation, and to my surprise I couldn't find any equivalent of the Java Map contract that two Maps with equal entrySet()s must be equal. 我把通过快速浏览一下DictionaryIDictionary文件,并让我吃惊我找不到了Java的任何等效Map合约两个Maps平等entrySet()s必须相等。 (The docs say that Dictionary -- not IDictionary -- overrides Equals() , but don't say what that override does.) (文档说Dictionary - not IDictionary - 覆盖了Equals() ,但没有说出覆盖的内容。)

So it looks like key-value equality in C# is a property of the Dictionary concrete class, not of the IDictionary interface. 因此,看起来C#中的键值相等是Dictionary具体类的属性,而不是IDictionary接口的IDictionary Is this right? 这是正确的吗? Is it generally true of the whole System.Collections framework? 通常是整个System.Collections框架都是如此吗?

If so, I'd be interested to read some discussion of why MS chose that approach -- and also of what the preferred way would be to check for equality of collection contents in C#. 如果是这样,我有兴趣阅读一些关于为什么MS选择这种方法的讨论 - 以及在C#中检查集合内容是否相等的首选方法。

And finally, I wouldn't mind a pointer to a well-tested ReadOnlyDictionary implementation. 最后,我不介意指向经过充分测试的ReadOnlyDictionary实现的指针。 :) :)


ETA: To be clear, I'm not looking for suggestions on how to test my implementation -- that's relatively trivial. ETA:要明确,我不是在寻找关于如何测试我的实现的建议 - 这是相对微不足道的。 I'm looking for guidance on what contract those tests should enforce. 我正在寻找有关这些测试应该强制执行的合同的指导 And why. 为什么呢。


ETA: Folks, I know IDictionary is an interface, and I know interfaces can't implement methods. ETA:伙计们,我知道 IDictionary是一个接口,我知道接口不能实现方法。 It's the same in Java. 在Java中也是如此。 Nevertheless, the Java Map interface documents an expectation of certain behavior from the equals() method. 然而,Java Map接口记录了equals()方法对某些行为的期望。 Surely there must be .NET interfaces that do things like this, even if the collection interfaces aren't among them. 当然必须有.NET接口执行这样的操作,即使集合接口不在其中。

Overriding equals is normally only done with classes which have a degree of value semantics (eg string ). 覆盖equals通常只对具有一定值语义的类(例如string )完成。 Reference equality is what people are more often concerned about with most reference types and a good default, especially in cases which can be less than clear (are two dictionaries with exactly the same key-value-pairs but different equality-comparers [and hence adding the same extra key-value-pair could make them now different] equal or not?) or where value-equality is not going to be frequently looked for. 引用相等是人们更常关注的大多数引用类型和良好的默认值,特别是在可能不太明确的情况下(两个字典具有完全相同的键值对但不同的相等比较器[并因此添加相同的额外键值对可以使它们现在变得相同或不相同?)或者不经常寻找值相等的地方。

After all, you are looking for a case where two different types are considered equal. 毕竟,您正在寻找两种不同类型被认为相同的情况。 An equality override would probably still fail you. 相等覆盖可能仍会使您失败。

All the more so as you can always create your own equality comparer quickly enough: 更是如此,因为你总能足够快地创建自己的相等比较器:

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;
        }
    }
}

That wouldn't serve all possible cases where one wants to compare dictionaries, but then, that was my point. 这并不适用于所有可能需要比较词典的情况,但那时,这就是我的观点。

Filling up the BCL with "probably what they mean" equality methods would be a nuisance, not a help. 用“可能是他们的意思”平等方法填补BCL将是一种麻烦,而不是帮助。

I would suggest using CollectionAssert.AreEquivalent() from NUnit. 我建议使用NUnit的CollectionAssert.AreEquivalent()。 Assert.AreEqual() is really not meant for collections. Assert.AreEqual()实际上不适用于集合。 http://www.nunit.org/index.php?p=collectionAssert&r=2.4 http://www.nunit.org/index.php?p=collectionAssert&r=2.4

For later readers, here's what I've been told / been able to figure out: 对于后来的读者,这是我被告知/能够弄清楚的:

  1. The contract for .NET collections, unlike Java collections, doesn't include any specific behavior for Equals() or GetHashCode() . 与Java集合不同,.NET集合的合同不包括Equals()GetHashCode()任何特定行为。
  2. LINQ Enumerable.SequenceEqual() extension method will work for ordered collections, including dictionaries -- which present as IEnumerable<KeyValuePair> ; LINQ Enumerable.SequenceEqual()扩展方法适用于有序集合,包括字典 - 以IEnumerable<KeyValuePair> ; KeyValuePair is a struct, and its Equals method uses reflection to compare the contents. KeyValuePair是一个结构,它的Equals方法使用反射来比较内容。
  3. Enumerable provides other extension methods that can be used to cobble together a content equality check, such as Union() and Intersect() . Enumerable提供了其他扩展方法,可用于拼凑内容相等性检查,例如Union()Intersect()

I'm coming around to the idea that, convenient as the Java methods are, they might not be the best idea if we're talking about mutable collections, and about the typical implicit equals() semantics -- that two equal objects are interchangeable. 我现在认为,方便的Java方法,如果我们讨论可变集合,以及典型的隐式equals()语义,它们可能不是最好的想法 - 两个equal对象是可互换的。 .NET doesn't provide very good support for immutable collections, but the open-source PowerCollections library does. .NET不能为不可变集合提供非常好的支持,但开源PowerCollections库却可以。

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;
        }
    }
}

So it looks like key-value equality in C# is a property of the Dictionary concrete class, not of the IDictionary interface. 因此,看起来C#中的键值相等是Dictionary具体类的属性,而不是IDictionary接口的属性。 Is this right? 这是正确的吗? Is it generally true of the whole System.Collections framework? 通常是整个System.Collections框架都是如此吗?

If so, I'd be interested to read some discussion of why MS chose that approach 如果是这样,我有兴趣阅读一些关于为什么MS选择这种方法的讨论

I think it is quite simple - IDictionary is an interface and interfaces can't have any implementations and in .NET world equality of two objects is defined through Equals method. 我认为这很简单 - IDictionary是一个接口,接口不能有任何实现,在.NET世界中,两个对象的相等性是通过Equals方法定义的。 So it is just impossible to override Equals for the IDictionary interface to allow it possesing "key-value equality". 因此,只有不可能为IDictionary接口重写Equals以允许它拥有“键值相等”。

you made a big mistake in your original post. 你在原帖中犯了大错。 You talked about the Equals() method in the IDictionary interface. 您在IDictionary接口中讨论了Equals()方法。 That's the point! 这才是重点!

Equals() is a virtual method of System.Object that classes can override. Equals()是类可以覆盖的System.Object的虚方法。 Interfaces don't implement methods at all. 接口根本不实现方法 Instead, instances of interfaces are reference types, thus inheriting from System.Object and potentially declaring an override of Equals() . 相反,接口的实例是引用类型,因此继承自System.Object可能声明重写Equals()

Now the point... System.Collections.Generic.Dictionary<K,V> does not override Equals. 现在的点... System.Collections.Generic.Dictionary<K,V> 没有重载equals。 You said you implemented your IDictionary your own way, and reasonably overriden Equals, but look at your own code 你说你以自己的方式实现了你的IDictionary,并合理地覆盖了Equals,但是看看你自己的代码

Assert.AreEqual (backingDictionary, readOnlyDictionary); 

This method is basically implemented as return backingDictionary.Equals(readOnlyDictionary) and again here is the point. 这个方法基本上实现为return backingDictionary.Equals(readOnlyDictionary) ,这里再说一遍。

Basic Equals() method returns false if two objects are instances of different classes, you cannot control that. 如果两个对象是不同类的实例,则基本Equals()方法返回false,您无法控制它。 Otherwise, if the two objects are of the same type, each member is compared via reflection (just members, not properties) using the Equals() approach instead of == (which is what the manual calls "value compare" instead of "reference compare") 否则,如果两个对象属于同一类型,则使用Equals()方法而不是==通过反射(只是成员,而不是属性)比较每个成员(这是手动调用“值比较”而不是“参考”)相比”)

So for first, I would not be surprised if Assert.AreEqual (readOnlyDictionary,backingDictionary); 所以首先,如果Assert.AreEqual (readOnlyDictionary,backingDictionary); ,我不会感到惊讶Assert.AreEqual (readOnlyDictionary,backingDictionary); succeeds, because it would trigger a user-defined Equals method. 成功,因为它会触发用户定义的Equals方法。

I have no doubts that approaches by other users in this thread work, but I just wanted to explain you what was the mistake in your original approach. 我毫不怀疑此线程中其他用户的方法是有效的,但我只是想解释一下原始方法中的错误是什么。 Surely Microsoft would have better implemented an Equals method that compares the current instance to any other IDictionary instance, but, again, that would have gone outside the scope of the Dictionary class, which is a public stand-alone class and is not meant to be the only public available implementation of IDictionary. 当然,微软最好实现一个Equals方法,将当前实例与任何其他IDictionary实例进行比较,但同样,这将超出Dictionary类的范围,这是一个公共独立类,并不意味着IDictionary唯一公开可用的实现。 For example, when you define an interface, a factory and a protected class that implements it in a library, you might want to compare the class against other instances of the base interface rather than of the class itself which is not public. 例如,当您定义一个接口,一个工厂和一个在库中实现它的受保护类时,您可能希望将该类与基本接口的其他实例进行比较,而不是与非公开的类本身进行比较。

I hope to have been of help to you. 我希望对你有所帮助。 Cheers. 干杯。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM