.NET Dictionary / IDictionary 的 Equals() 合约与 Java Map 的 equals() 合约
怀念 Collections.unmodifyingMap()
,我一直在实现一个基于 这个讨论,我的单元测试很快遇到了一个问题:
Assert.AreEqual (backingDictionary, readOnlyDictionary);
失败,即使键值对成对匹配。我又玩了一下,看起来至少(感谢西蒙尼)
Assert.AreEquals (backingDictionary, new Dictionary<..> { /* same contents */ });
确实通过了。
我快速浏览了 Dictionary
和 IDictionary
文档,令我惊讶的是,我找不到任何与 Java Map
约定两个具有相同 entrySet()s
的 Map
必须相等。 (文档说 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
重写 equals 通常只适用于具有一定程度值语义的类(例如
string
)。引用相等性是人们更常关心的大多数引用类型和良好的默认值,特别是在不太清楚的情况下(两个字典具有完全相同的键值对但不同的相等比较器[因此添加相同的额外键值对可能使它们现在不同]相等或不相等?)或者不会经常寻找值相等的地方。毕竟,您正在寻找两种不同类型被视为相等的情况。平等覆盖可能仍然会让你失败。
更重要的是,您总是可以足够快地创建自己的相等比较器:
这并不能满足人们想要比较字典的所有可能情况,但这就是我的观点。
用“可能是他们的意思”的平等方法来填充 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:
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.
我建议使用 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
对于后来的读者,这是我被告知/能够弄清楚的:
与 Java 集合不同,它不
包括任何特定行为
Equals()
或GetHashCode()
。Enumerable.SequenceEqual()
扩展方法适用于
订购的集合,包括
字典——呈现为
IEnumerable
;KeyValuePair
是一个结构体,它的Equals
方法使用反射来比较内容。
Enumerable
提供其他扩展可以用来鹅卵石的方法
一起进行内容平等检查,例如
如
Union()
和Intersect()
。我的想法是,尽管 Java 方法很方便,但如果我们谈论可变集合以及典型的隐式
equals()
语义,它们可能不是最好的主意 - - 两个相等
对象是可以互换的。 .NET 没有为不可变集合提供很好的支持,但开源 PowerCollections 库可以提供。For later readers, here's what I've been told / been able to figure out:
unlike Java collections, doesn't
include any specific behavior for
Equals()
orGetHashCode()
.Enumerable.SequenceEqual()
extension method will work for
ordered collections, including
dictionaries -- which present as
IEnumerable<KeyValuePair>
;KeyValuePair
is a struct, and itsEquals
method uses reflectionto compare the contents.
Enumerable
provides other extensionmethods that can be used to cobble
together a content equality check, such
as
Union()
andIntersect()
.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 twoequal
objects are interchangeable. .NET doesn't provide very good support for immutable collections, but the open-source PowerCollections library does.它非常简单 -
IDictionary
是一个接口,接口不能有任何实现,在 .NET 世界中,两个对象的相等性是通过Equals
方法定义的。因此,不可能重写 IDictionary 接口的 Equals 来使其拥有“键值相等”。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 throughEquals
method. So it is just impossible to overrideEquals
for the IDictionary interface to allow it possesing "key-value equality".你在原来的帖子中犯了一个很大的错误。您谈到了
IDictionary
接口中的Equals()
方法。这就是重点!Equals() 是类可以重写的 System.Object 的虚拟方法。接口根本不实现方法。相反,接口的实例是引用类型,因此继承自
System.Object
并可能声明对Equals()
的重写。现在的重点是...
System.Collections.Generic.Dictionary
确实不覆盖 Equals。您说您以自己的方式实现了 IDictionary,并合理地覆盖了 Equals,但是看看您自己的代码。此方法基本上实现为
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 theIDictionary
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 fromSystem.Object
and potentially declaring an override ofEquals()
.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 codeThis 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.