简体   繁体   中英

How to compare two lists that contain objects that contain Dictionaries?

I have two Lists (List “A” and List “B”) that hold objects of type “KeyStore”, which is shown below:

public class KeyStore
{
    public Dictionary<string, string> PrimaryKeys { get; set; }

    public KeyStore(string pkName, string pkValue)
    {
        PrimaryKeys = new Dictionary<string, string> {{pkName, pkValue}};
    }

    public KeyStore()
    {
        PrimaryKeys = new Dictionary<string, string>();
    }
}

I need to look at each record in List “A” and see if there is a matching record in List “B”. If there is, then this record needs to be stored in a new list that contains just matching records. A match is considered true if a record's PrimaryKeys dictionary contains the same number of entries and the same key value combination as a record in List “B”. The order of the entries in the dictionary is not important in testing for equality. If there is a record in List “A” that does not have a match in List “B”, then this needs to be stored in a new list that will only contain records found in List “A”.

Previously I did something similar when I had Lists of strings where I used “Intersect” and “Except” to create lists of matched and non-matched records. I'm assuming that now that I need to compare these KeyStore objects I need to go up a level of complexity. Can anyone offer a solution or advise on how I should approach this problem?

EDIT 1 ----------------

Based on comments, I have created a class that implements IEqualityComparer, as shown below:

class KeyStoreComparer : IEqualityComparer<KeyStore>
{
    public bool Equals(KeyStore x, KeyStore y)
    {
        if (x != null && x.PrimaryKeys.Count == y.PrimaryKeys.Count)
        {
            return x.PrimaryKeys.Keys.All(k => y.PrimaryKeys.ContainsKey(k)) &&
                   x.PrimaryKeys.Keys.All(k => x.PrimaryKeys[k].Equals(y.PrimaryKeys[k]));
        }
        return false;
    }

    public int GetHashCode(KeyStore obj)
    {
        return ReferenceEquals(obj, null) ? 0 : obj.GetHashCode();
    }
}

I have created some dummy data but when the "Intersect" command is run the above code is never called. Any ideas where I am going wrong?

var ListA = new List<KeyStore>();
ListA.Add(new KeyStore("a", "b"));
ListA.Add(new KeyStore("c", "d"));

var ListB = new List<KeyStore>();
ListB.Add(new KeyStore("a", "b"));
ListB.Add(new KeyStore("x", "y"));

var g = ListA.Intersect(ListB, new KeyStoreComparer());

The code in the "Equals" and "GetHashCode" may not be correct but I'm just trying to get it to get as far as running it before I can improve it.

EDIT 2 ---------------------------------------

I have made various changes to the KeyStore class as shown in the example by “fox” on this page. I still don't get the overridden functions to be called. As an experiment I tried this:

var result = ListA.Equals(ListB);

When I do this the overridden functions in the KeyStor class don't run. But if I do this:

var result = ListA[0].Equals(ListB[0]);

The overridden functions do run and give the expected result. Anyone know how I can get this to work for all items in the lists rather than just for individual records?

EDIT 3 ---------------------------------------

The problem I am seeing is that the override works fine for single items, eg:

var a = new KeyStore("a", "b");
var b = new KeyStore("a", "b");
var c = a.Equals(b);

When I run the above my break point on the KeyStore "Equals" function is hit. As soon as I try to do something similar but with a List of KeyStore, the breakpoint is no longer hit. Do I need to do something extra when working with Lists?

public class KeyStore
{
    public Dictionary<string, string> PrimaryKeys { get; set; }

    public KeyStore(string pkName, string pkValue)
    {
        PrimaryKeys = new Dictionary<string, string> { { pkName, pkValue } };
    }

    public KeyStore()
    {
        PrimaryKeys = new Dictionary<string, string>();
    }

    public override bool Equals(object obj)
    {
        // If parameter is null return false.
        if (obj == null)
            return false;

        // If parameter cannot be cast to KeyStore return false.
        KeyStore targetKeyStore = obj as KeyStore;
        if (targetKeyStore == null)
            return false;

        return PrimaryKeys.OrderBy(pk => pk.Key).SequenceEqual(targetKeyStore.PrimaryKeys.OrderBy(pk => pk.Key));
    }

    public override int GetHashCode()
    {
        StringBuilder content = new StringBuilder();

        foreach (var item in PrimaryKeys.OrderBy(pk => pk.Key))
            content.AppendFormat("{0}-{1}", item.Key, item.Value);

        return content.ToString().GetHashCode();
    }
}

Eric Lippert's guide on implementing GetHashCode wrote that "equal items have equal hashes" . GetHashCode() implementation above just to show concept, might not suitable for production code.

Overriding the ToString method will help simplify your code quite a bit. See if this helps:

public class KeyStore
{
    public SortedDictionary<string, string> PrimaryKeys
    {
        get;
        set;
    }

    public KeyStore(string pkName, string pkValue)
    {
        PrimaryKeys = new SortedDictionary<string, string> { { pkName, pkValue } };
    }

    public KeyStore()
    {
        PrimaryKeys = new SortedDictionary<string, string>();
    }

    public override bool Equals(object obj)
    {
        if(obj == null || (KeyStore)obj == null)
            return false;
        KeyStore temp = (KeyStore)obj;

        return ToString() == temp.ToString();
    }

    public override int GetHashCode()
    {
        return ToString().GetHashCode();
    }
    public override string ToString()
    {
        return PrimaryKeys.Count.ToString() + " : \n" + string.Join("\n",(from kvp in PrimaryKeys
                                                                          let s = kvp.Key + " - " + kvp.Value
                                                                          select s));
    }
}

List<KeyStore> Lista = new List<KeyStore>
{
    new KeyStore("testa","testa1"),
    new KeyStore("testb","testb1"),
    new KeyStore("testc", "testc1")
};
List<KeyStore> Listb = new List<KeyStore>
{
    new KeyStore("testa","testa1"),
    new KeyStore("testd","testb1"),
    new KeyStore("testc", "testa1"),
    new KeyStore("teste", "teste1")
};
var Listc = Lista.Intersect(Listb).ToList();
var Listd = Lista.Except(Listb).ToList();

?Listc
Count = 1
    [0]: {1 : 
testa - testa1}

?Listd
Count = 2
    [0]: {1 : 
testb - testb1}
    [1]: {1 : 
testc - testc1}

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