.NET Dictionary / IDictionary 的 Equals() 合约与 Java Map 的 equals() 合约

发布于 2024-09-27 01:11:51 字数 1822 浏览 5 评论 0原文

怀念 Collections.unmodifyingMap(),我一直在实现一个基于 这个讨论,我的单元测试很快遇到了一个问题:

Assert.AreEqual (backingDictionary, readOnlyDictionary);

失败,即使键值对成对匹配。我又玩了一下,看起来至少(感谢西蒙尼)

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

确实通过了。

我快速浏览了 DictionaryIDictionary 文档,令我惊讶的是,我找不到任何与 Java Map 约定两个具有相同 entrySet()sMap必须相等。 (文档说 Dictionary -- not IDictionary -- 覆盖 Equals(),但没有说覆盖的作用。)

所以看起来 C# 中的键值相等是 Dictionary 具体类的属性,而不是 IDictionary 接口的属性。这是对的吗?整个 System.Collections 框架通常都是如此吗?

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

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


预计到达时间:需要明确的是,我不是寻找有关如何测试我的实现的建议——这相对来说是微不足道的。我正在寻找有关这些测试应执行哪些合同的指导。以及原因。


预计到达:各位,我知道 IDictionary 是一个接口,而且我知道接口无法实现方法。在Java中也是一样的。尽管如此,Java Map 接口记录了 来自 equals() 方法的某些行为。当然,一定有 .NET 接口可以执行此类操作,即使集合接口不在其中。

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:

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)

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. (The docs say that Dictionary -- not IDictionary -- overrides Equals(), but don't say what that override does.)

So it looks like key-value equality in C# is a property of the Dictionary concrete class, not of the IDictionary interface. Is this right? Is it generally true of the whole System.Collections framework?

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#.

And finally, I wouldn't mind a pointer to a well-tested ReadOnlyDictionary implementation. :)


ETA: To be clear, I'm not looking for suggestions on how to test my implementation -- that's relatively trivial. 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. It's the same in Java. Nevertheless, the Java Map interface documents an expectation of certain behavior from the equals() method. Surely there must be .NET interfaces that do things like this, even if the collection interfaces aren't among them.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

孤独患者 2024-10-04 01:11:51

重写 equals 通常只适用于具有一定程度值语义的类(例如 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 会很麻烦,而不是有帮助。

Overriding equals is normally only done with classes which have a degree of value semantics (e.g. 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.

初见你 2024-10-04 01:11:51

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

I would suggest using CollectionAssert.AreEquivalent() from NUnit. Assert.AreEqual() is really not meant for collections. http://www.nunit.org/index.php?p=collectionAssert&r=2.4

黑白记忆 2024-10-04 01:11:51

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

  1. .NET 集合的契约,
    与 Java 集合不同,它不
    包括任何特定行为
    Equals()GetHashCode()
  2. LINQ Enumerable.SequenceEqual()
    扩展方法适用于
    订购的集合,包括
    字典——呈现为
    IEnumerable;
    KeyValuePair 是一个结构体,它的
    Equals 方法使用反射
    来比较内容。
  3. Enumerable 提供其他扩展
    可以用来鹅卵石的方法
    一起进行内容平等检查,例如
    Union()Intersect()

我的想法是,尽管 Java 方法很方便,但如果我们谈论可变集合以及典型的隐式 equals() 语义,它们可能不是最好的主意 - - 两个相等对象是可以互换的。 .NET 没有为不可变集合提供很好的支持,但开源 PowerCollections 库可以提供。

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().
  2. LINQ Enumerable.SequenceEqual()
    extension method will work for
    ordered collections, including
    dictionaries -- which present as
    IEnumerable<KeyValuePair>;
    KeyValuePair is a struct, and its
    Equals method uses reflection
    to compare the contents.
  3. Enumerable provides other extension
    methods that can be used to cobble
    together a content equality check, such
    as Union() and 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. .NET doesn't provide very good support for immutable collections, but the open-source PowerCollections library does.

蘸点软妹酱 2024-10-04 01:11:51
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;
        }
    }
}
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;
        }
    }
}
沫雨熙 2024-10-04 01:11:51

所以看起来键值相等
C# 是字典的一个属性
具体类,而不是 IDictionary 的类
界面。这是对的吗?是吗
一般而言,整体正确
System.Collections 框架?

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

它非常简单 - IDictionary 是一个接口,接口不能有任何实现,在 .NET 世界中,两个对象的相等性是通过 Equals 方法定义的。因此,不可能重写 IDictionary 接口的 Equals 来使其拥有“键值相等”。

So it looks like key-value equality in
C# is a property of the Dictionary
concrete class, not of the IDictionary
interface. Is this right? Is it
generally true of the whole
System.Collections framework?

If so, I'd be interested to read some
discussion of why MS chose that
approach

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. So it is just impossible to override Equals for the IDictionary interface to allow it possesing "key-value equality".

橘虞初梦 2024-10-04 01:11:51

你在原来的帖子中犯了一个很大的错误。您谈到了 IDictionary 接口中的 Equals() 方法。这就是重点!

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

现在的重点是... System.Collections.Generic.Dictionary 确实覆盖 Equals。您说您以自己的方式实现了 IDictionary,并合理地覆盖了 Equals,但是看看您自己的代码。

Assert.AreEqual (backingDictionary, readOnlyDictionary); 

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

如果两个对象是不同类的实例,基本 Equals() 方法将返回 false,这是您无法控制的。否则,如果两个对象属于相同类型,则使用 Equals() 方法而不是 == 通过反射(仅是成员,而不是属性)比较每个成员(这就是手册所说的“值比较”而不是“引用比较”)

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

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

我希望对您有所帮助。
干杯。

you made a big mistake in your original post. You talked about the Equals() method in the IDictionary interface. That's the point!

Equals() is a virtual method of System.Object that classes can override. 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().

Now the point... System.Collections.Generic.Dictionary<K,V> does not override Equals. You said you implemented your IDictionary your own way, and reasonably overriden Equals, but look at your own code

Assert.AreEqual (backingDictionary, readOnlyDictionary); 

This method is basically implemented as return backingDictionary.Equals(readOnlyDictionary) and again here is the point.

Basic Equals() method returns false if two objects are instances of different classes, you cannot control that. 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")

So for first, I would not be surprised if Assert.AreEqual (readOnlyDictionary,backingDictionary); succeeds, because it would trigger a user-defined Equals method.

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. 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文