简体   繁体   中英

Compare equality of two objects based on dictionaries

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.

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());

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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM