怀念Collections.unmodifiableMap()
,我一直在实施基于this discussion的只读IDictionary
包装器,我的单元测试很快遇到了问题:
Assert.AreEqual (backingDictionary, readOnlyDictionary);
即使键值对匹配,也会失败。我玩了一下,看起来至少(谢谢西蒙尼)
Assert.AreEquals (backingDictionary, new Dictionary<..> { /* same contents */ });
确实通过了。
我快速查看了Dictionary
和IDictionary
文档,令我惊讶的是,我找不到任何与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接口执行这样的操作,即使集合接口不在其中。
答案 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)
对于后来的读者,这是我被告知/能够弄清楚的:
Equals()
或GetHashCode()
。Enumerable.SequenceEqual()
扩展方法将起作用
有序集合,包括
词典 - 表现为
IEnumerable<KeyValuePair>
;
KeyValuePair
是一个结构,它的结构
Equals
方法uses reflection
比较内容。Enumerable
提供其他扩展程序
可以用来拼凑的方法
一起进行内容平等检查
作为Union()
和Intersect()
。我正在接受这样的想法,就像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唯一公开可用的实现。例如,当您定义一个接口,一个工厂和一个在库中实现它的受保护类时,您可能希望将该类与基本接口的其他实例进行比较,而不是与非公开的类本身进行比较。
我希望对你有所帮助。 欢呼声。