21

This test fails:

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
    }

Am I misunderstanding something about how a Dictionary works?

I'm looking for the Java equivalent of .equals(), not trying to check referential equality.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Nick Heiner
  • 119,074
  • 188
  • 476
  • 699

6 Answers6

27

Dictionary class does not override Object.Equals method as seen from MSDN doco:

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

Determines whether the specified Object is equal to the current Object.

Seeing that you are doing unit testing, your Assert class should provide a test method for testing if two collections are the same.

Microsoft Unit testing framework provides CollectionAssert class for the purpose of comparing collections:

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

EDIT Dictionary implements ICollection interface, can you see if that just works? You might need to use this overload to compare two dictionary entries.

EDIT Hmm IDictionary does not implement ICollection, which is a bit of a pain. This however works (albeit a 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);

THe above approach will work for instances of Dictionary, however if you are testing a method that returns IDictionary it might fail if the implmentation changes. My advice is to change the code to use Dictionary instead of IDictionary (since IDictionary is not readonly, so you are not hiding all that much by using that instead of concreate Dictionary).

Igor Zevaka
  • 74,528
  • 26
  • 112
  • 128
  • Looks good. But how do I convert from a `Dictionary` to `ICollection`? A `ICollection` only has one type argument. – Nick Heiner Feb 08 '10 at 01:03
  • I've been looking at that documentation but I can't find anything that I could use to compare dictionaries. – Nick Heiner Feb 08 '10 at 01:15
  • That is intriguing, but what `ICompare` would I use? Do I have to write my own? The cast to `ICollection` doesn't work. What type would I use as the argument? The key or the value? – Nick Heiner Feb 08 '10 at 01:24
  • Your above hack wouldn't compile for me. The error: Using the generic type 'System.Collections.Generic.ICollection' requires '1' type arguments – Nick Heiner Feb 08 '10 at 01:26
  • Try `System.Collections.ICollection`. – Igor Zevaka Feb 08 '10 at 01:28
  • 1
    `CollectionAssert.AreEqual` would work only if both dictionaries enumerators enumerate the elements from the dictionaries in the same order, and I wouldn't expect that to be guaranteed – yannick1976 Jun 09 '23 at 15:05
15

If you are specifically interested in how you can fix this from unit testing perspective:

Try this

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

Explanation

There are extension methods on IDictionary - such as .ToList() - available in .Net 3.5 and up, which will convert the dictionary into a collection of KeyValuePair that can be easily compared with CollectionAssert.AreEquivalent.

They'll even give reasonably helpful error messages! Example usage:

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).     
bacar
  • 9,761
  • 11
  • 55
  • 75
  • Are you sure about this? As far as I know CollectionAssert is an extension method in unit testing framework like NUnit and MSTest - not in .Net. – Kjetil Klaussen Nov 06 '12 at 07:36
  • 1
    The context of the question was unit testing, although I grant you that it was not explicitly limited to that scope. Given that, I think the answer is useful to both the OP and others who come across this looking for a solution for testing. I'm surprised someone found my answer so incorrect/unhelpful/wrong-headed as to actually give it a down vote. – bacar Nov 06 '12 at 08:15
  • Same [comment](https://stackoverflow.com/questions/2219047/net-dictionaries-have-same-keys-and-values-but-arent-equal#comment134787589_2219057) as for the accepted answer: I don't think you can rely on dictionaries element to be enumerated in any specific order. – yannick1976 Jun 09 '23 at 15:10
  • @yannick1976, `CollectionAssert.AreEquivalent ` does not care about the order. – bacar Jun 09 '23 at 19:16
7

The problem is with this line of code:

Assert.AreEqual(dict, dictClone)

You are comparing object references, which aren't equal.

bacar
  • 9,761
  • 11
  • 55
  • 75
dcp
  • 54,410
  • 22
  • 144
  • 164
  • +1, additionally I'm not sure if MS's test suite has collection compare tools, but I'm pretty sure NUnit does. – user7116 Feb 08 '10 at 00:59
  • It might be because it isn't obvious you're highlighting the failing line. (*2 years ago when you answered, I thought it obvious.*) – user7116 May 01 '12 at 17:14
  • @sixlettervariables - It seemed obvious to me 2 years ago, and it still does today. I mean, I have the highlighted line right above the explanation, not sure what else I could have done. – dcp May 01 '12 at 18:15
  • It confounded me too - it looks like it might be a proposed solution - I've edited the original answer to clarify. – bacar Jun 18 '12 at 14:21
4

I have used an extension method that checks two sequences for equal items

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;
}
Rohan West
  • 9,262
  • 3
  • 37
  • 64
  • +1 Thank you this lesson in Linq ! Being able to study this type of code is very valuable to a "mere mortal" like me :) – BillW Feb 08 '10 at 02:07
  • @BillW, There is nothing about LINQ here. This is a feature called extension method which of course was developed mainly for LINQ. – Fakrudeen Feb 08 '10 at 06:56
  • 1
    Nice! One thing to note though; you should check for NULL-values before you do 'source.Count()' and 'destination.Count()'. – Kjetil Klaussen Nov 06 '12 at 07:37
1

You are completely not understanding how reference types work.

Dictionary does not override object.Equals(). Thus, it uses reference equality - basically, if both references are pointing to the same instance, they're equal, otherwise they aren't.

Anon.
  • 58,739
  • 8
  • 81
  • 86
0

The NUnit class CollectionAssert has an AreEquivalent method which accepts IEnumerable as parameters, so in that case it's as simple as

CollectionAssert.AreEquivalent(dict, dictClone);

because Dictionary implements IEnumerable.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
  • MSTest also includes `CollectionAssert.AreEquivalent` and it works for dictionaries in the same way. Please include that in this post. – Sebastian Jan 18 '23 at 12:20