【问题标题】:.NET Dictionaries have same keys and values, but aren't "equal".NET 字典具有相同的键和值,但不“相等”
【发布时间】:2020-04-03 16:32:48
【问题描述】:

此测试失败:

using Microsoft.VisualStudio.TestTools.UnitTesting;        

[TestMethod()]
        public void dictEqualTest() {
            IDictionary<string, int> dict = new Dictionary<string, int>();
            IDictionary<string, int> dictClone = new Dictionary<string, int>();

        for (int x = 0; x < 3; x++) {
            dict[x.ToString()] = x;
            dictClone[x.ToString()] = x;
        }

        Assert.AreEqual(dict, dictClone); // fails here
        Assert.IsTrue(dict.Equals(dictClone)); // and here, if the first is commented out
        Assert.AreSame(dict, dictClone); // also fails
    }

我是否误解了 Dictionary 的工作原理?

我正在寻找 .equals() 的 Java 等效项,而不是尝试检查引用相等性。

【问题讨论】:

  • 我将从堆栈跟踪开始 - AreEqual 调用哪个方法?顺便问一下,这是MBUnit、NUnit、MS Test/其他吗?
  • 这是内置的 Visual Studio 2008 Pro 单元测试。

标签: c# .net dictionary equality


【解决方案1】:

字典类不会覆盖从 MSDN 文档中看到的 Object.Equals 方法:

http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx

判断是否指定 对象等于当前对象。

看到你在做单元测试,你的 Assert 类应该提供一个测试方法来测试两个集合是否相同。

Microsoft 单元测试框架提供CollectionAssert 类用于比较集合:

http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.collectionassert_members%28VS.80%29.aspx

EDIT Dictionary 实现了ICollection 接口,你能看看它是否有效吗?您可能需要使用this overload 来比较两个字典条目。

EDIT 嗯,IDictionary 没有实现ICollection,这有点让人头疼。然而,这有效(尽管是 hack):

IDictionary<string, int> dict = new Dictionary<string, int>();
IDictionary<string, int> dictClone = new Dictionary<string, int>();

for(int x = 0; x < 3; x++) {
    dict[x.ToString()] = x;
    dictClone[x.ToString()] = x;
}

CollectionAssert.AreEqual((System.Collections.ICollection)dict, (System.Collections.ICollection)dictClone);

上述方法适用于Dictionary 的实例,但是如果您正在测试返回IDictionary 的方法,则如果实现发生更改,它可能会失败。我的建议是更改代码以使用Dictionary 而不是IDictionary(因为IDictionary 不是只读的,所以你不会通过使用它而不是concreate Dictionary 隐藏那么多)。

【讨论】:

  • 看起来不错。但是如何从Dictionary 转换为ICollectionICollection 只有一个类型参数。
  • 我一直在查看该文档,但找不到可以用来比较字典的任何内容。
  • 这很有趣,但我会使用什么ICompare?我必须自己写吗? ICollection 的演员表不起作用。我将使用什么类型作为参数?键还是值?
  • 您上面的 hack 无法为我编译。错误:使用泛型类型 'System.Collections.Generic.ICollection' 需要 '1' 类型参数
  • 试试System.Collections.ICollection
【解决方案2】:

如果您对如何从单元测试的角度解决此问题特别感兴趣:

试试这个

CollectionAssert.AreEquivalent(dict.ToList(), dictClone.ToList());

说明

extension methods on IDictionary - 例如.ToList() - 在.Net 3.5 及更高版本中可用,它将字典转换为KeyValuePair 的集合,可以很容易地与CollectionAssert.AreEquivalent 进行比较。

他们甚至会给出相当有用的错误信息!示例用法:

IDictionary<string, string> d1 = new Dictionary<string, string> {
    { "a", "1"}, {"b", "2"}, {"c", "3"}};

IDictionary<string, string> d2 = new Dictionary<string, string> {
    {"b", "2"}, { "a", "1"}, {"c", "3"}}; // same key-values, different order

IDictionary<string, string> d3 = new Dictionary<string, string> {
    { "a", "1"}, {"d", "2"}, {"c", "3"}}; // key of the second element differs from d1

IDictionary<string, string> d4 = new Dictionary<string, string> {
    { "a", "1"}, {"b", "4"}, {"c", "3"}}; // value of the second element differs from d1

CollectionAssert.AreEquivalent(d1.ToList(), d2.ToList());
//CollectionAssert.AreEquivalent(d1.ToList(), d3.ToList()); // fails!
//CollectionAssert.AreEquivalent(d1.ToList(), d4.ToList()); // fails!

// if uncommented, the 2 tests above fail with error:
//   CollectionAssert.AreEquivalent failed. The expected collection contains 1
//   occurrence(s) of <[b, 2]>. The actual collection contains 0 occurrence(s).     

【讨论】:

  • 你确定吗?据我所知,CollectionAssert 是 NUnit 和 MSTest 等单元测试框架中的一种扩展方法——不在 .Net 中。
  • 问题的上下文是单元测试,尽管我承认它并没有明确限制在该范围内。鉴于此,我认为该答案对 OP 和其他遇到此问题并寻找测试解决方案的人都很有用。我很惊讶有人发现我的回答如此不正确/无用/错误,以至于实际上投了反对票。
【解决方案3】:

问题出在这行代码上:

Assert.AreEqual(dict, dictClone)

您正在比较不相等的对象引用。

【讨论】:

  • +1,另外我不确定 MS 的测试套件是否有集合比较工具,但我很确定 NUnit 有。
  • 这可能是因为您突出显示失败的行并不明显。 (2 年前你回答时,我认为这很明显。
  • @sixlettervariables - 两年前对我来说似乎很明显,今天仍然如此。我的意思是,我在解释上方有突出显示的行,不知道我还能做什么。
  • 它也让我感到困惑 - 看起来它可能是一个建议的解决方案 - 我已经编辑了原始答案以澄清。
【解决方案4】:

我使用了一种扩展方法来检查两个序列是否相等

public static bool CheckForEquality<T>(this IEnumerable<T> source, IEnumerable<T> destination)
{
    if (source.Count() != destination.Count())
    {
        return false;
    }

    var dictionary = new Dictionary<T, int>();

    foreach (var value in source)
    {
        if (!dictionary.ContainsKey(value))
        {
            dictionary[value] = 1;
        }
        else
        {
            dictionary[value]++;
        }
    }

    foreach (var member in destination)
    {
        if (!dictionary.ContainsKey(member))
        {
            return false;
        }

        dictionary[member]--;
    }

    foreach (var kvp in dictionary)
    {
        if (kvp.Value != 0)
        {
            return false;
        }
    }

    return true;
}

【讨论】:

  • +1 感谢 Linq 的这一课!能够研究这种类型的代码对于像我这样的“凡人”来说非常有价值:)
  • @BillW,这里没有关于 LINQ 的内容。这是一个称为扩展方法的功能,当然主要是为 LINQ 开发的。
  • 不错!不过要注意一件事;在执行“source.Count()”和“destination.Count()”之前,您应该检查 NULL 值。
【解决方案5】:

您完全不了解引用类型的工作原理。

Dictionary 不会覆盖object.Equals()。因此,它使用引用相等 - 基本上,如果两个引用都指向同一个实例,则它们相等,否则它们不相等。

【讨论】:

    【解决方案6】:

    NUnit 类 CollectionAssert 有一个 AreEquivalent method which accepts IEnumerable as parameters,所以在这种情况下它很简单

    CollectionAssert.AreEquivalent(dict, dictClone);
    

    因为Dictionary 实现了IEnumerable

    【讨论】:

      猜你喜欢
      • 2020-12-18
      • 1970-01-01
      • 2013-05-07
      • 1970-01-01
      • 2011-02-22
      • 1970-01-01
      • 1970-01-01
      • 2014-04-27
      • 1970-01-01
      相关资源
      最近更新 更多