2

I have two objects with these definitions:

public static Dictionary<string, Container> cont1 = new Dictionary<string, Container>();
public static Dictionary<string, Container> cont2 = new Dictionary<string, Container>();

The schema of Container class is as following:

public class Container
{
    public string IDx { get; set; }
    public string IDy { get; set; }
    public string Name { get; set; }
    public Dictionary<string, Sub> Subs = new Dictionary<string, Sub>();
}

public class Sub
{
    public string Namex { get; set; }
    public string Namey { get; set; }
    public string Value { get; set; }
    public Dictionary<string, string> Paths { get; set; }
}

My question is: How can I deep check the equity of cont1 and cont2? I mean the equality of every member and value even deep down within Subs objects;

Is there any functionality in c# for such situations or I have to write a custom method for checking equality based on the structure of the objects myself;

Second Question: I can obviate the equality problem if I can create two different copies of Products; I mean say we have a base Container object with all the members and values and then create two separate copies of Container, namely cont1 and cont2 which changing a value in cont1 wont change the same value in cont2.

Note1: this method for cloning is not working:

cont2 = new Dictionary<string, Container>(cont1);

Note2: most of the proposed methods in other answers are based on a one level dictionary (using loops or LINQ for checking) and not such a case when we have properties and dictionary objects (which having their own properties) within the object.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
wiki
  • 1,877
  • 2
  • 31
  • 47
  • `My question is: How can I deep check the equity of cont1 and cont2` - does that mean both dictionaries have the same keys. and each key's value has the same exact *values* or the same exact *instances* of `Container`? – Jamiec May 12 '16 at 08:06
  • Of course you have to write a custom method for checking equality based on the structure of the objects yourself. But it's not even clear what you understand as equal. Also, don't ask multiple questions at once. – Tim Schmelter May 12 '16 at 08:08
  • They may have different keys and values and that's the point; to check if they have exactly the same keys, properties with the same values – wiki May 12 '16 at 08:10
  • @wiki: do you need to know if they are equal or which are in one that are not in the other? – Tim Schmelter May 12 '16 at 08:13
  • no; I just need to know whether they are exactly equal or not; the differences don't matter – wiki May 12 '16 at 08:15

2 Answers2

2

A Dictionary is a Sequence, so in general what you're probably looking for is Enumerable<T>.SequenceEquals which allows passing in an IEquityComparer<T>.

Your sequence (Dictionary) is an IEnumerable<KeyValuePair<string,Container>> so you need an comparer which implements IEquityComparer<IEnumerable<KeyValuePair<string,Container>>> (Thats a lot of angle braces!).

var equal = cont1.SequenceEquals(cont2, new StringContainerPairEquityComparer());

Note that the order of elements dictionaries is not guaranteed, so to use the method properly you should probably use OrderBy before comparing sequences - however this adds to the inefficiency of this method.


For your second question, what you're trying to do is Clone the dictionary. In general your Container should implement ICloneable interface, which you can then use to create a copy

var cont2 = cont1.ToDictionary(k => k.Key, v => v.Value.Clone());
Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • Yes; you are right; and that's why I'm asking this question, to know if there is an easier way – wiki May 12 '16 at 08:17
  • easier than implementing a long method based on the structure of my classes to check for equality – wiki May 12 '16 at 08:18
  • @wiki ah I see - no there is no shortcut here. – Jamiec May 12 '16 at 08:18
  • what about creating two unrelated copies? any comment on that? – wiki May 12 '16 at 08:20
  • @wiki updated answer - sorry, missed your second question (thats why second questions should be a separate question!) – Jamiec May 12 '16 at 08:23
  • `SequenceEquals` is not the proper way to check if two dictionaries are equal. A dictionary is not meant to be an ordered sequence and it's also irrelevant for the equals check. `SequenceEquals` will always take the order into account which is unpredictable in a dictionary. – Tim Schmelter May 12 '16 at 08:42
  • @TimSchmelter - you're right, of course. Ordering both `cont1` and `cont2` by the dictionary key perhaps would alleviate that problem - but hardly efficient. Yours is the better solution by far. – Jamiec May 12 '16 at 08:50
1

Yes, you have to write a custom method for checking equality based on the structure of the objects yourself. I would provide a custom IEqualityComparer<Container> and an IEqualityComparer<Sub> like here (GetHashCode implementation based on this):

public class ContainerCheck : IEqualityComparer<Container>
{
    private SubCheck subChecker = new SubCheck();
    public bool Equals(Container x, Container y)
    {
        if (ReferenceEquals(x, y))
            return true;
        if (x == null || y == null)
            return false;
        if (x.IDx != y.IDx || x.IDy != y.IDy || x.Name != y.Name)
            return false;
        // check dictionary
        if (ReferenceEquals(x.Subs, y.Subs))
            return true;
        if (x.Subs == null || y.Subs == null || x.Subs.Count != y.Subs.Count)
            return false;
        foreach (var kv in x.Subs)
            if (!y.Subs.ContainsKey(kv.Key) || subChecker.Equals(y.Subs[kv.Key], kv.Value))
                return false;
        return true;

    }

    public int GetHashCode(Container obj)
    {
        unchecked // Overflow is fine, just wrap
        {
            int hash = 17;
            // Suitable nullity checks etc, of course :)
            hash = hash * 23 + obj.IDx.GetHashCode();
            hash = hash * 23 + obj.IDy.GetHashCode();
            hash = hash * 23 + obj.Name.GetHashCode();
            foreach (var kv in obj.Subs)
            {
                hash = hash * 23 + kv.Key.GetHashCode();
                hash = hash * 23 + subChecker.GetHashCode(kv.Value);
            }

            return hash;
        }
    }
}

public class SubCheck : IEqualityComparer<Sub>
{
    public bool Equals(Sub x, Sub y)
    {
        if (ReferenceEquals(x, y))
            return true;
        if (x == null || y == null)
            return false;
        if (x.Namex != y.Namex || x.Namey != y.Namey || x.Value != y.Value)
            return false;
        // check dictionary
        if (ReferenceEquals(x.Paths, y.Paths))
            return true;
        if (x.Paths == null || y.Paths == null || x.Paths.Count != y.Paths.Count)
            return false;
        foreach(var kv in x.Paths)
            if (!y.Paths.ContainsKey(kv.Key) || y.Paths[kv.Key] != kv.Value)
                return false;
        return true;
    }

    public int GetHashCode(Sub obj)
    {
        unchecked // Overflow is fine, just wrap
        {
            int hash = 17;
            // Suitable nullity checks etc, of course :)
            hash = hash * 23 + obj.Namex.GetHashCode();
            hash = hash * 23 + obj.Namey.GetHashCode();
            hash = hash * 23 + obj.Value.GetHashCode();
            foreach (var kv in obj.Paths)
            {
                hash = hash * 23 + kv.Key.GetHashCode();
                hash = hash*23 + kv.Value.GetHashCode();
            }

            return hash;
        }
    }
} 

This should deep check all properties and the dictionaries. Then you could use following loop to compare both dictionaries with each other:

bool equal = true;
var allKeys = cont1.Keys.Concat(cont2.Keys).ToList();
var containerChecker = new ContainerCheck();

foreach (string key in allKeys)
{
    Container c1;
    Container c2;
    if (!cont1.TryGetValue(key, out c1) || !cont2.TryGetValue(key, out c2))
    {
        equal = false;
    }
    else
    {
        // deep check both containers
        if (!containerChecker.Equals(c1, c2))
            equal = false;
    }
    if(!equal)
        break;  // or collect differences
}
Community
  • 1
  • 1
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • Thanks a lot for proving the full implementation! anyway, I was more curious to know if there was any shortcut for such situations or not – wiki May 12 '16 at 08:37
  • @wiki: no, unfortunately not. You always have `Object.ReferenceEquals` as also used in my implementation. But if you don't override `Equals`+`GetHashCode`yourself(or provide an `IEqualityComparer`) all that .NET will use is `ReferenceEquals`. So only checks if both are the same references and not if the properties are equal. – Tim Schmelter May 12 '16 at 08:38