23 Stimmen

Vergleichen Sie zwei Wörterbücher für gleiche Daten in c#?

Ich habe zwei Wörterbücher, die einen String-Schlüssel und dann ein Objekt enthalten. Das Objekt enthält fünf Felder. Gibt es einen eleganten Weg, um sicherzustellen, dass beide Wörterbücher zunächst die gleichen Schlüssel und dann, wenn dies korrekt ist, die gleichen fünf Felder pro Objekt enthalten?

Hätten die beiden Wörterbücher denselben eingebauten Hashcode oder ähnliches?

EDIT, scheint für den folgenden Code nicht zu funktionieren:

Dictionary<string, MyClass> test1 = new Dictionary<string, MyClass>();
Dictionary<string, MyClass> test2 = new Dictionary<string, MyClass>();

MyClass i = new MyClass("", "", 1, 1, 1, 1);
MyClass j = new MyClass("", "", 1, 1, 1, 1);

test1.Add("1", i);
test2.Add("1", j);

bool equal = test1.OrderBy(r => r.Key).SequenceEqual(test2.OrderBy(r => r.Key));

class MyClass
{
    private string a;
    private string b;
    private long? c;
    private decimal d;
    private decimal e;
    private decimal f;

    public MyClass(string aa, string bb, long? cc, decimal dd, decimal ee, decimal ff)
    {
        a= aa;
        b= bb;
        c= cc;
        d= dd;
        e= ee;
        f= ff;
    }

das Ergebnis false?

30voto

Rawling Punkte 47404

Sie können verwenden

bool dictionariesEqual = 
    dic1.Keys.Count == dic2.Keys.Count &&
    dic1.Keys.All(k => dic2.ContainsKey(k) && object.Equals(dic2[k], dic1[k]));

23voto

Habib Punkte 211435

Zunächst müssen Sie die Equals y GetHashCode Methode in Ihrer Klasse, da sonst der Vergleich mit Referenzen statt mit tatsächlichen Werten durchgeführt wird. (Der Code zum Überschreiben von Equals y GetHashCode wird am Ende bereitgestellt) , danach können Sie es verwenden:

var result = (dic1 == dic2) || //Reference comparison (if both points to same object)
             (dic1.Count == dic2.Count && !dic1.Except(dic2).Any());

Da die die Reihenfolge, in der die Elemente im Dictionary zurückgegeben werden, ist nicht festgelegt können Sie sich nicht auf Dictionary.SequenceEqual (ohne OrderBy ) .

Sie können es versuchen:

Dictionary<string, object> dic1 = new Dictionary<string, object>();
Dictionary<string, object> dic2 = new Dictionary<string, object>();
dic1.Add("Key1", new { Name = "abc", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key2", new { Name = "DEF", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key3", new { Name = "GHI", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key4", new { Name = "JKL", Number = "123", Address = "def", Loc = "xyz" });

dic2.Add("Key1",new { Name = "abc",Number=  "123", Address= "def", Loc="xyz"});
dic2.Add("Key2", new { Name = "DEF", Number = "123", Address = "def", Loc = "xyz" });
dic2.Add("Key3", new { Name = "GHI", Number = "123", Address = "def", Loc = "xyz" });
dic2.Add("Key4", new { Name = "JKL", Number = "123", Address = "def", Loc = "xyz" });

bool result = dic1.SequenceEqual(dic2); //Do not use that

Die meisten der Zeit, in der die obige Frage beantwortet wird true aber darauf kann man sich aufgrund der ungeordneten Natur der Dictionary .

Desde SequenceEqual vergleicht den Auftrag ebenfalls und verlässt sich dabei auf nur SequenceEqual sein könnte falsch . Sie müssen Folgendes verwenden OrderBy um beide Wörterbücher zu bestellen, und verwenden Sie dann SequenceEqual mögen:

bool result2 = dic1.OrderBy(r=>r.Key).SequenceEqual(dic2.OrderBy(r=>r.Key));

Dies erfordert jedoch mehrere Iterationen, eine für die Bestellung und die andere für den Vergleich der einzelnen Elemente mit SequenceEqual .

Code zum Überschreiben Equals y GetHashCode

private class MyClass
{
    private string a;
    private string b;
    private long? c;
    private decimal d;
    private decimal e;
    private decimal f;

    public MyClass(string aa, string bb, long? cc, decimal dd, decimal ee, decimal ff)
    {
        a = aa;
        b = bb;
        c = cc;
        d = dd;
        e = ee;
        f = ff;
    }

    protected bool Equals(MyClass other)
    {
        return string.Equals(a, other.a) && string.Equals(b, other.b) && c == other.c && e == other.e && d == other.d && f == other.f;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((MyClass)obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var hashCode = (a != null ? a.GetHashCode() : 0);
            hashCode = (hashCode * 397) ^ (b != null ? b.GetHashCode() : 0);
            hashCode = (hashCode * 397) ^ c.GetHashCode();
            hashCode = (hashCode * 397) ^ e.GetHashCode();
            hashCode = (hashCode * 397) ^ d.GetHashCode();
            hashCode = (hashCode * 397) ^ f.GetHashCode();
            return hashCode;
        }
    }
}

Sie können auch sehen: Korrekte Art, Equals() und GetHashCode() zu überschreiben

1voto

Sebastian Negraszus Punkte 11525

Die eingebaute Equals Funktion von Dictionary<T> prüft nur auf Referenzgleichheit, siehe diese Frage zu SO . Hashcodes sagen nicht zuverlässig aus, ob zwei Objekte gleich sind; es besteht immer die Möglichkeit einer Hash-Kollision. Verwenden Sie niemals Hashcodes als Gleichheitstest!

Ich würde es von Hand machen: Vergleichen Sie die Anzahl der Einträge in beiden Wörterbüchern, iterieren Sie über die Schlüssel-Wert-Paare des einen Wörterbuchs und prüfen Sie, ob der Schlüssel im anderen Wörterbuch vorhanden ist, und vergleichen Sie die entsprechenden Objekte in beiden Wörterbüchern. Edita: Siehe die Antwort von Rawling :)

1voto

greyseal96 Punkte 786

Es gab ein paar Antworten, die meiner Meinung nach ziemlich nahe dran waren, aber es gab ein paar zusätzliche Punkte, die meiner Meinung nach hinzugefügt werden sollten, also füge ich sie als weitere mögliche Antwort hinzu.

Erstens würde ich die Verwendung der Methode SequenceEquals vermeiden. Sie ist eine Erweiterungsmethode für Enumerable und setzt implizit voraus, dass die beiden Sammlungen in der gleichen Reihenfolge vorliegen. Dictionaries sind nicht dazu gedacht, geordnete Sammlungen zu sein, so dass die Verwendung von SequenceEquals bedeutet, dass Sie unnötigerweise über beide Dictionaries iterieren müssen, um sortierte/geordnete Zwischensammlungen zu erstellen, die Sie auch nicht benötigen, und dann über diese Sammlungen iterieren müssen, um sie auf Gleichheit zu vergleichen. Das scheint wirklich ineffizient und ein Missbrauch von LINQ zu sein, alles im Namen des Versuchs, sich kurz zu fassen und eine einzeilige Lösung zu schreiben. Wenn der OP's Idee von "elegant" ist kurz, ich denke, dies wird den Trick tun, aber es scheint verschwenderisch.

Andererseits, wenn die Vorstellung des Auftraggebers von "elegant" effizient ist, dann müssen Sie wahrscheinlich etwas mehr Code schreiben. Zunächst sollten Sie entweder die Equals-Methode für Ihre Klasse außer Kraft setzen oder IEquatable in Ihrer Klasse implementieren (siehe aquí zum Beispiel). So können Sie die Werte im Wörterbuch vergleichen. Dann werden Sie wahrscheinlich etwas tun wollen, wie eine Schnittstelle wie IEqualityComparer für Ihr Wörterbuch zu implementieren.

Dann würde der Vergleich der beiden Wörterbücher etwa wie folgt aussehen. Es ist nur ein schnelles und schmutziges "Back of the Napkin"-Beispiel, also kein Beispiel für den besten Weg, es zu tun, aber es soll einen Weg illustrieren, nur so oft wie nötig über das Wörterbuch zu iterieren und abzubrechen, sobald eine Ungleichheit gefunden wird.

Zunächst der erforderliche Code:

public class Foo
{
    //members here...
    public override bool Equals(object obj)
    {
        //implementation here
    }
    //You should probably also override GetHashCode to be thorough,
    //but that's an implementation detail...
}

//This method could stand on its own or you could change it to make it 
//part of the implementation of one of the comparison interfaces...
bool DictionariesEqual(Dictionary<K, V> x, Dictionary<K, V> y)
{
    //If we're comparing the same object, it's obviously equal to itself.
    if(x == y)
    {
        return true;
    }
    //Make sure that we don't have null objects because those are
    //definitely not equal.
    if (x == null || y == null)
    {
        return false;
    }

    //Stop processing if at any point the dictionaries aren't equal.
    bool result = false;

    //Make sure that the dictionaries have the same count.
    result = x.Count == y.Count;

    //If we passed that check, keep going.
    if(result)
    {
        foreach(KeyValuePair<K, V> xKvp in x)
        {
            //If we don't have a key from one in the other, even though
            //the counts are the same, the dictionaries aren't equal so
            //we can fail out.
            V yValue;
            if(!y.TryGetValue(xKvp.Key, out yValue))
            {
                result = false;
                break;
            }
            else
            {
                //Use the override of the Equals method for your object
                //to see if the value from y is equal to the value from
                //x.
                result = xKvp.Value.Equals(yValue);
                if(!result)
                {
                    //If they're not equal we can just quit out.
                    break;
                }
            }
        }
    }
    return result;
}

Dann würden wir es so verwenden:

Dictionary<string, Foo> dict1 = new Dictionary<string, Foo>();
Dictionary<string, Foo> dict2 = new Dictionary<string, Foo>();
//Fill the dictionaries here...

//Compare the dictionaries
bool areDictsEqual = DictionariesEqual(dict1, dict2);

Es ist also nicht der kürzeste Code, aber es wird auch nicht mehr als nötig iteriert. Meiner Meinung nach ist das eleganter.

Edit: Ein Kommentator schlug vor, die Codebeispiele zu aktualisieren, um sie allgemeiner zu machen. Ursprünglich habe ich mich an das Beispiel des ursprünglichen Posters gehalten, aber es schien mir eine gute Idee zu sein, es allgemeiner zu machen, mit einem Vorbehalt: Seien Sie vorsichtig, was der Typ des Schlüssels ist. Wenn es ein einfacher Typ wie ein String oder ein int ist, für den C# Gleichheitsimplementierungen enthält, sollte alles in Ordnung sein. Wenn der Schlüssel ein benutzerdefinierter Typ ist, müssen Sie sicherstellen, dass Sie die Funktion Equals() y GetHashCode() für Ihren benutzerdefinierten Typ.

0voto

Philipp Punkte 639

In diesem Fall können Sie einfach die SequenceEquals()-Methode verwenden, wie folgt:

   Dictionary<string, object> d1 = new Dictionary<string, object>();
   d1.Add("first", new { Name = "TestName", Age = 12, ID = 001 }); 

   Dictionary<string, object> d2 = new Dictionary<string, object>();
   d2.Add("first", new { Name = "TestName", Age = 12, ID = 001 });

   Console.WriteLine(d1.SequenceEqual(d2)); //outputs True                

Hinweis: Der Einfachheit halber habe ich implizite Klassen verwendet, um die Dictionaries zu füllen. Der Code funktioniert mit allen Objekten auf die gleiche Weise. Die Hashcodes der beiden Dictionaries sind nicht gleich, was man leicht überprüfen kann, indem man folgendes tut:

   Console.WriteLine(d1.GetHashCode() + " " + d2.GetHashCode()); //outputs different hashcodes

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X