Vergleichen von Objekteigenschaften in c # [geschlossen]

111

Dies ist, was ich als Methode für eine Klasse entwickelt habe, die von vielen meiner anderen Klassen geerbt wurde. Die Idee ist, dass es den einfachen Vergleich zwischen Eigenschaften von Objekten desselben Typs ermöglicht.

Nun, das funktioniert - aber um die Qualität meines Codes zu verbessern, dachte ich, ich würde ihn zur Überprüfung wegwerfen. Wie kann es besser / effizienter / etc. Sein?

/// <summary>
/// Compare property values (as strings)
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool PropertiesEqual(object comparisonObject)
{

    Type sourceType = this.GetType();
    Type destinationType = comparisonObject.GetType();

    if (sourceType == destinationType)
    {
        PropertyInfo[] sourceProperties = sourceType.GetProperties();
        foreach (PropertyInfo pi in sourceProperties)
        {
            if ((sourceType.GetProperty(pi.Name).GetValue(this, null) == null && destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null) == null))
            {
                // if both are null, don't try to compare  (throws exception)
            }
            else if (!(sourceType.GetProperty(pi.Name).GetValue(this, null).ToString() == destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null).ToString()))
            {
                // only need one property to be different to fail Equals.
                return false;
            }
        }
    }
    else
    {
        throw new ArgumentException("Comparison object must be of the same type.","comparisonObject");
    }

    return true;
}
Nailitdown
quelle
3
Übrigens ist Ihnen diese SE-Site bekannt: codereview.stackexchange.com
Wip
Es gibt einige Objektvergleichsbibliotheken: CompareNETObjects , DeepEqual , AutoCompare , ZCompare ...
Nawfal
... und eine Menge generischer Gleichheitsvergleichsimplementierer, von denen einige sind: MemberwiseEqualityComparer , Equ , SemanticComparison , EqualityComparer , DeepEqualityComparer , Equality , Equals.Fody . Die letztere Gruppe könnte in Bezug auf Umfang und Flexibilität in Bezug auf das, was sie erreichen kann, eingeschränkt sein.
Nawfal
Ich wähle diese Frage als Wegthema zu schließen , weil sie gehört Code - Review
Xiaoy312

Antworten:

160

Ich suchte nach einem Codeausschnitt, der etwas Ähnliches tun würde, um beim Schreiben von Komponententests zu helfen. Folgendes habe ich letztendlich verwendet.

public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class 
  {
     if (self != null && to != null)
     {
        Type type = typeof(T);
        List<string> ignoreList = new List<string>(ignore);
        foreach (System.Reflection.PropertyInfo pi in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
        {
           if (!ignoreList.Contains(pi.Name))
           {
              object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
              object toValue = type.GetProperty(pi.Name).GetValue(to, null);

              if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
              {
                 return false;
              }
           }
        }
        return true;
     }
     return self == to;
  }

BEARBEITEN:

Gleicher Code wie oben, verwendet jedoch die Methoden LINQ und Extension:

public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
            from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            where !ignoreList.Contains(pi.Name) && pi.GetUnderlyingType().IsSimpleType() && pi.GetIndexParameters().Length == 0
            let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
            let toValue = type.GetProperty(pi.Name).GetValue(to, null)
            where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
            select selfValue;
        return !unequalProperties.Any();
    }
    return self == to;
}

public static class TypeExtensions
   {
      /// <summary>
      /// Determine whether a type is simple (String, Decimal, DateTime, etc) 
      /// or complex (i.e. custom class with public properties and methods).
      /// </summary>
      /// <see cref="http://stackoverflow.com/questions/2442534/how-to-test-if-type-is-primitive"/>
      public static bool IsSimpleType(
         this Type type)
      {
         return
            type.IsValueType ||
            type.IsPrimitive ||
            new[]
            {
               typeof(String),
               typeof(Decimal),
               typeof(DateTime),
               typeof(DateTimeOffset),
               typeof(TimeSpan),
               typeof(Guid)
            }.Contains(type) ||
            (Convert.GetTypeCode(type) != TypeCode.Object);
      }

      public static Type GetUnderlyingType(this MemberInfo member)
      {
         switch (member.MemberType)
         {
            case MemberTypes.Event:
               return ((EventInfo)member).EventHandlerType;
            case MemberTypes.Field:
               return ((FieldInfo)member).FieldType;
            case MemberTypes.Method:
               return ((MethodInfo)member).ReturnType;
            case MemberTypes.Property:
               return ((PropertyInfo)member).PropertyType;
            default:
               throw new ArgumentException
               (
                  "Input MemberInfo must be if type EventInfo, FieldInfo, MethodInfo, or PropertyInfo"
               );
         }
      }
   }
Taras Alenin
quelle
Big T - ein ziemlicher Oldie, aber definitiv ein großartiger Zweck für Tests und einfache Vergleiche. Danke +1
Jim Tollan
1
Das ist gut, aber ich habe festgestellt, dass es nicht mit komplexeren Objekten funktioniert. Zum Beispiel habe ich ein Objekt mit einigen Zeichenfolgen (es vergleicht sie gut), aber dann hat dieses Objekt auch eine Liste eines anderen Objekts, das es nicht richtig vergleicht, also muss ich das irgendwie wiederholen.
Ryan Thomas
1
Ich musste im ersten Kriterium zwei weitere Kriterien hinzufügen, da Sie indizierte Eigenschaften ausschließen müssen, die in anderen Fällen eine Ausnahme auslösen. Hier sind die Kriterien für diesen Fehler: pi.GetIndexParameters (). Length == 0. Das zweite Kriterium zur Lösung des von @RyanThomas angegebenen Problems lautet: pi.GetUnderlyingType (). IsSimpleType (). Wie Sie sehen werden, ist IsSimpleType eine Erweiterung, die für den Klassentyp nicht vorhanden ist. Ich habe die Antwort geändert, um alle diese Bedingungen und die Erweiterung hinzuzufügen.
Samuel
64

UPDATE: Die neueste Version von Compare-Net-Objects befindet sich auf GitHub und enthält das NuGet-Paket und das Tutorial . Es kann wie genannt werden

//This is the comparison class
CompareLogic compareLogic = new CompareLogic();

ComparisonResult result = compareLogic.Compare(person1, person2);

//These will be different, write out the differences
if (!result.AreEqual)
    Console.WriteLine(result.DifferencesString);

Oder verwenden Sie, wenn Sie eine Konfiguration ändern müssen

CompareLogic basicComparison = new CompareLogic() 
{ Config = new ComparisonConfig()
   { MaxDifferences = propertyCount 
     //add other configurations
   }
};

Die vollständige Liste der konfigurierbaren Parameter finden Sie in ComparisonConfig.cs

Ursprüngliche Antwort:

Die Einschränkungen, die ich in Ihrem Code sehe:

  • Das größte ist, dass es keinen tiefen Objektvergleich macht.

  • Es wird kein Element-für-Element-Vergleich durchgeführt, wenn Eigenschaften Listen sind oder Listen als Elemente enthalten (dies kann n-Ebenen sein).

  • Es wird nicht berücksichtigt, dass bestimmte Arten von Eigenschaften nicht verglichen werden sollten (z. B. eine Func-Eigenschaft, die zu Filterzwecken verwendet wird, wie die in der PagedCollectionView-Klasse).

  • Es wird nicht nachverfolgt, welche Eigenschaften tatsächlich unterschiedlich waren (sodass Sie dies in Ihren Behauptungen anzeigen können).

Ich habe heute nach einer Lösung für Unit-Test-Zwecke gesucht, um einen tiefen Vergleich von Eigenschaften zu Eigenschaften durchzuführen, und am Ende habe ich Folgendes verwendet: http://comparenetobjects.codeplex.com .

Es ist eine kostenlose Bibliothek mit nur einer Klasse, die Sie einfach so verwenden können:

var compareObjects = new CompareObjects()
{
    CompareChildren = true, //this turns deep compare one, otherwise it's shallow
    CompareFields = false,
    CompareReadOnly = true,
    ComparePrivateFields = false,
    ComparePrivateProperties = false,
    CompareProperties = true,
    MaxDifferences = 1,
    ElementsToIgnore = new List<string>() { "Filter" }
};

Assert.IsTrue(
    compareObjects.Compare(objectA, objectB), 
    compareObjects.DifferencesString
);

Es kann auch leicht für Silverlight neu kompiliert werden. Kopieren Sie einfach die eine Klasse in ein Silverlight-Projekt und entfernen Sie eine oder zwei Codezeilen für Vergleiche, die in Silverlight nicht verfügbar sind, wie z. B. den Vergleich privater Mitglieder.

Liviu Trifoi
quelle
2
Liviu, ich habe Ihren Kommentar bemerkt, dass die Klasse nicht mit Silverlight kompatibel ist. Ich habe es gerade so geändert, dass es mit Silverlight und Windows Phone 7 kompatibel ist. Siehe Änderungssatz 74131 unter compareenetobjects.codeplex.com/SourceControl/list/changesets
Greg Finzer
Das sieht vielversprechend aus.
Ich
Vielen Dank für das tolle Beispiel! Die IgnoreObjectTypesEinstellung kann auch nützlich sein, wenn es verschiedene Typen gibt.
Sergey Brunov
Version 2.0 hat eine Portable Class Library-Version, die mit Silverlight 5+, Windows Phone 8+, WinRT 8+, Xamarin IOS und Xamarin Droid
kompatibel ist
DifferencesStringwurde in der CompareObjects-Klasse korrigiert. Aber jetzt können Sie das stattdessen aus dem Vergleichsergebnis erhalten:var r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
Mariano Desanze
6

Ich denke, es wäre am besten, dem Muster für Override Object # Equals () zu folgen.
Für eine bessere Beschreibung: Lesen Sie Bill Wagners effektives C # - Punkt 9, denke ich

public override Equals(object obOther)
{
  if (null == obOther)
    return false;
  if (object.ReferenceEquals(this, obOther)
    return true;
  if (this.GetType() != obOther.GetType())
    return false;
  # private method to compare members.
  return CompareMembers(this, obOther as ThisClass);
}
  • Auch bei Methoden, die auf Gleichheit prüfen, sollten Sie entweder true oder false zurückgeben. Entweder sind sie gleich oder nicht. Geben Sie false zurück, anstatt eine Ausnahme auszulösen.
  • Ich würde in Betracht ziehen, Object # Equals zu überschreiben.
  • Auch wenn Sie dies berücksichtigt haben müssen, ist die Verwendung von Reflection zum Vergleichen von Eigenschaften angeblich langsam (ich habe keine Zahlen, um dies zu sichern). Dies ist das Standardverhalten für valueType # Equals in C #. Es wird empfohlen, Equals für Werttypen zu überschreiben und einen mitgliedsbezogenen Vergleich der Leistung durchzuführen. (Früher habe ich dies schnell gelesen, da Sie eine Sammlung von benutzerdefinierten Eigenschaftsobjekten haben ... meine schlechte.)

Update-Dez 2011:

  • Wenn der Typ bereits eine Produktion Equals () hat, benötigen Sie natürlich einen anderen Ansatz.
  • Wenn Sie dies verwenden, um unveränderliche Datenstrukturen ausschließlich zu Testzwecken zu vergleichen, sollten Sie Produktionsklassen keine Equals hinzufügen (Jemand könnte die Tests durch Ändern der Equals-Implementierung abspritzen oder die Erstellung einer produktionsbedingten Equals-Implementierung verhindern). .
Gishu
quelle
Ich habe Probleme beim Überschreiben von .Equals (), weil ich versuche, dies auf einer Basisklasse zu implementieren, die vererbt wird ... weil ich die Schlüssel für die Klasse nicht kenne, für die dies ausgeführt wird, kann ich nicht Implementieren Sie eine anständige Überschreibung für GetHasCode () (erforderlich, wenn Sie Equals () überschreiben).
Nailitdown
Voraussetzung ist, dass wenn objA.Equals (objB), dann objA.GetHashCode () == objB.GetHashCode (). GetHashCode sollte nicht von veränderlichen Zuständen / Daten einer Klasse abhängig sein ... Ich habe nicht verstanden, was Sie mit Schlüsseln für die Klasse gemeint haben. Scheint etwas zu sein, das gelöst werden kann. Hat der Basistyp nicht die 'Schlüssel'?
Gishu
6

Wenn die Leistung keine Rolle spielt, können Sie sie serialisieren und die Ergebnisse vergleichen:

var serializer = new XmlSerializer(typeof(TheObjectType));
StringWriter serialized1 = new StringWriter(), serialized2 = new StringWriter();
serializer.Serialize(serialized1, obj1);
serializer.Serialize(serialized2, obj2);
bool areEqual = serialized1.ToString() == serialized2.ToString();
Edward Brey
quelle
4
Wenn Sie dies vor einiger Zeit versucht haben, würden Sie sich fragen, wie viele Objekte nicht serialisierbar sind ...
Offler
5

Ich denke, die Antwort von Big T war ziemlich gut, aber der tiefe Vergleich fehlte, also habe ich ihn ein wenig optimiert:

using System.Collections.Generic;
using System.Reflection;

/// <summary>Comparison class.</summary>
public static class Compare
{
    /// <summary>Compare the public instance properties. Uses deep comparison.</summary>
    /// <param name="self">The reference object.</param>
    /// <param name="to">The object to compare.</param>
    /// <param name="ignore">Ignore property with name.</param>
    /// <typeparam name="T">Type of objects.</typeparam>
    /// <returns><see cref="bool">True</see> if both objects are equal, else <see cref="bool">false</see>.</returns>
    public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
    {
        if (self != null && to != null)
        {
            var type = self.GetType();
            var ignoreList = new List<string>(ignore);
            foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (ignoreList.Contains(pi.Name))
                {
                    continue;
                }

                var selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                var toValue = type.GetProperty(pi.Name).GetValue(to, null);

                if (pi.PropertyType.IsClass && !pi.PropertyType.Module.ScopeName.Equals("CommonLanguageRuntimeLibrary"))
                {
                    // Check of "CommonLanguageRuntimeLibrary" is needed because string is also a class
                    if (PublicInstancePropertiesEqual(selfValue, toValue, ignore))
                    {
                        continue;
                    }

                    return false;
                }

                if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
                {
                    return false;
                }
            }

            return true;
        }

        return self == to;
    }
}
Greg
quelle
4

Ich würde der PublicInstancePropertiesEqual-Methode die folgende Zeile hinzufügen, um Fehler beim Kopieren und Einfügen zu vermeiden:

Assert.AreNotSame(self, to);
thanei
quelle
2

Überschreiben Sie .ToString () für alle Ihre Objekte in den Eigenschaften? Andernfalls könnte dieser zweite Vergleich mit null zurückkommen.

Auch in diesem zweiten Vergleich bin ich am Zaun über das Konstrukt von! (A == B) im Vergleich zu (A! = B) in Bezug auf die Lesbarkeit in sechs Monaten / zwei Jahren. Die Linie selbst ist ziemlich breit, was in Ordnung ist, wenn Sie einen breiten Monitor haben, aber möglicherweise nicht sehr gut drucken. (Nitpick)

Verwenden alle Ihre Objekte immer Eigenschaften, sodass dieser Code funktioniert? Könnte es einige interne, nicht besessene Daten geben, die sich von Objekt zu Objekt unterscheiden können, aber alle offengelegten Daten sind gleich? Ich denke an einige Daten, die sich im Laufe der Zeit ändern könnten, wie zwei Zufallszahlengeneratoren, die zufällig an einem Punkt dieselbe Zahl treffen, aber zwei verschiedene Informationssequenzen erzeugen, oder nur Daten, die nicht verfügbar gemacht werden über die Eigenschaftsschnittstelle.

mmr
quelle
gute Punkte -! = ... vereinbart, Punkt genommen. ToString () war ein Versuch, das Problem zu umgehen .GetValue gab ein Objekt zurück (daher ist der Vergleich immer falsch, da es sich um einen Referenzvergleich handelt). Gibt es einen besseren Weg?
Nailitdown
Wenn GetValue ein Objekt zurückgibt, können Sie diese Funktion erneut durchlaufen? dh EigenschaftenEqual für die zurückgegebenen Objekte aufrufen?
mmr
1

Wenn Sie nur Objekte desselben Typs oder weiter unten in der Vererbungskette vergleichen, geben Sie den Parameter als Basistyp und nicht als Objekt an.

Führen Sie auch Nullprüfungen für den Parameter durch.

Außerdem würde ich 'var' verwenden, um den Code besser lesbar zu machen (wenn es sich um den C # 3-Code handelt).

Wenn das Objekt Referenztypen als Eigenschaften hat, rufen Sie einfach ToString () auf, wodurch die Werte nicht wirklich verglichen werden. Wenn ToString nicht überschrieben wird, wird nur der Typname als Zeichenfolge zurückgegeben, die falsch positive Ergebnisse zurückgeben kann.

DarkwingDuck
quelle
Guter Punkt zu den Referenztypen - in meinem Fall spielt es keine Rolle, aber es besteht eine gute Chance, dass dies der Fall ist.
Nailitdown
1

Das erste, was ich vorschlagen würde, wäre, den tatsächlichen Vergleich so aufzuteilen, dass er etwas besser lesbar ist (ich habe auch den ToString () herausgenommen - wird das benötigt?):

else {
    object originalProperty = sourceType.GetProperty(pi.Name).GetValue(this, null);
    object comparisonProperty = destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null);

    if (originalProperty != comparisonProperty)
        return false;

Der nächste Vorschlag wäre, den Einsatz von Reflexion so gering wie möglich zu halten - es ist sehr langsam. Ich meine, sehr langsam. Wenn Sie dies tun, würde ich vorschlagen, die Eigenschaftsreferenzen zwischenzuspeichern. Ich bin mit der Reflection-API nicht sehr vertraut. Wenn dies nicht der Fall ist, passen Sie sie einfach an, damit sie kompiliert wird:

// elsewhere
Dictionary<object, Property[]> lookupDictionary = new Dictionary<object, Property[]>;

Property[] objectProperties = null;
if (lookupDictionary.ContainsKey(sourceType)) {
  objectProperties = lookupProperties[sourceType];
} else {
  // build array of Property references
  PropertyInfo[] sourcePropertyInfos = sourceType.GetProperties();
  Property[] sourceProperties = new Property[sourcePropertyInfos.length];
  for (int i=0; i < sourcePropertyInfos.length; i++) {
    sourceProperties[i] = sourceType.GetProperty(pi.Name);
  }
  // add to cache
  objectProperties = sourceProperties;
  lookupDictionary[object] = sourceProperties;
}

// loop through and compare against the instances

Ich muss jedoch sagen, dass ich den anderen Postern zustimme. Das riecht faul und ineffizient. Sie sollten stattdessen IComparable implementieren :-).

Tsimon
quelle
Ich habe mir nur IComparable angesehen, aber es schien, als wäre es zum Sortieren und Bestellen gedacht. Ist es wirklich nützlich, um die Gleichheit zweier Objekte zu vergleichen?
Nailitdown
Absolut, weil .Equals (Objekt o) wie folgt definiert ist. CompareTo (o) == 0. Equals verwendet also ComparesTo (), um die Gleichheit zu bestimmen. Dies ist viel effizienter (und üblicher) als die Verwendung von Reflexion.
Tsimon
Ich kann mich irren, wenn ich annehme, dass Equals in Bezug auf CompareTo () implementiert ist (oder implementiert werden sollte). Sie sollten in Betracht ziehen, Equals wie hier beschrieben zu überschreiben: stackoverflow.com/questions/104158/…
Tsimon
1

Hier wird eins überarbeitet, um null = null als gleich zu behandeln

 private bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
        {
            if (self != null && to != null)
            {
                Type type = typeof(T);
                List<string> ignoreList = new List<string>(ignore);
                foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (!ignoreList.Contains(pi.Name))
                    {
                        object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                        object toValue = type.GetProperty(pi.Name).GetValue(to, null);
                        if (selfValue != null)
                        {
                            if (!selfValue.Equals(toValue))
                                return false;
                        }
                        else if (toValue != null)
                            return false;
                    }
                }
                return true;
            }
            return self == to;
        }
Hossein
quelle
Was wäre, wenn ich ein tiefes Objektdiagramm hätte? Wie kann ich oben am besten eine Liste alter und neuer Eigenschaften zurückgeben, die geändert wurden?
Rod
1

Am Ende habe ich das gemacht:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb)
            {
                return false;
            }
        }
        return true;
    }

Verwendung:

    if (Compare<ObjectType>(a, b))

Aktualisieren

Wenn Sie einige Eigenschaften nach Namen ignorieren möchten:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b, params string[] ignore)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb && ignore.Where(x => x == a.GetType().GetProperties()[i].Name).Count() == 0)
            {
                return false;
            }
        }
        return true;
    }

Verwendung:

    if (MyFunction.Compare<ObjType>(a, b, "Id","AnotherProp"))
BjarkeCK
quelle
1

Sie können Ihren Code optimieren, indem Sie GetProperties nur einmal pro Typ aufrufen:

public static string ToStringNullSafe(this object obj)
{
    return obj != null ? obj.ToString() : String.Empty;
}
public static bool Compare<T>(T a, T b, params string[] ignore)
{
    var aProps = a.GetType().GetProperties();
    var bProps = b.GetType().GetProperties();
    int count = aProps.Count();
    string aa, bb;
    for (int i = 0; i < count; i++)
    {
        aa = aProps[i].GetValue(a, null).ToStringNullSafe();
        bb = bProps[i].GetValue(b, null).ToStringNullSafe();
        if (aa != bb && ignore.Where(x => x == aProps[i].Name).Count() == 0)
        {
            return false;
        }
    }
    return true;
}
Moti Elbilya
quelle
1

Der Vollständigkeit halber möchte ich auf http://www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflection verweisen. Es hat eine vollständigere Logik als die meisten anderen Antworten auf dieser Seite.

Allerdings ziehe ich Vergleichen-Net-Objects - Bibliothek https://github.com/GregFinzer/Compare-Net-Objects (genannt von Liviu Trifoi ‚s Antwort )
Die Bibliothek hat NuGet Paket http://www.nuget.org/packages/ CompareNETObjects und mehrere zu konfigurierende Optionen.

Michael Freidgeim
quelle
1

Stellen Sie sicher, dass keine Objekte vorhanden sind null.

Haben obj1und obj2:

if(obj1 == null )
{
   return false;
}
return obj1.Equals( obj2 );
Ric Tokyo
quelle
Was ist, wenn beide null sind? sind sie dann nicht gleich?
mmr
Guter Punkt für Nullen, in meinem Fall scheint die Verwendung von .Equals () nicht zu funktionieren, weshalb ich mir diese Lösung
ausgedacht
Nun, der Fall, auf den ich teste, sind zwei Objekte, eines neu erstellt, eines aus der Sitzung. Der Vergleich der beiden mit .Equals () gibt false zurück, obwohl beide identische Eigenschaftswerte haben
Nagel nach unten
0

Dies funktioniert auch dann, wenn die Objekte unterschiedlich sind. Sie können die Methoden in der Utilities-Klasse anpassen. Vielleicht möchten Sie auch private Eigenschaften vergleichen ...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

class ObjectA
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }

    public string FieldA;
    public DateTime FieldB;
}

class ObjectB
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }


    public string FieldA;
    public DateTime FieldB;


}

class Program
{
    static void Main(string[] args)
    {
        // create two objects with same properties
        ObjectA a = new ObjectA() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };
        ObjectB b = new ObjectB() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };

        // add fields to those objects
        a.FieldA = "hello";
        b.FieldA = "Something differnt";

        if (a.ComparePropertiesTo(b))
        {
            Console.WriteLine("objects have the same properties");
        }
        else
        {
            Console.WriteLine("objects have diferent properties!");
        }


        if (a.CompareFieldsTo(b))
        {
            Console.WriteLine("objects have the same Fields");
        }
        else
        {
            Console.WriteLine("objects have diferent Fields!");
        }

        Console.Read();
    }
}

public static class Utilities
{
    public static bool ComparePropertiesTo(this Object a, Object b)
    {
        System.Reflection.PropertyInfo[] properties = a.GetType().GetProperties(); // get all the properties of object a

        foreach (var property in properties)
        {
            var propertyName = property.Name;

            var aValue = a.GetType().GetProperty(propertyName).GetValue(a, null);
            object bValue;

            try // try to get the same property from object b. maybe that property does
                // not exist! 
            {
                bValue = b.GetType().GetProperty(propertyName).GetValue(b, null);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
                continue;

            if (aValue == null && bValue != null)
                return false;

            if (aValue != null && bValue == null)
               return false;

            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }



    public static bool CompareFieldsTo(this Object a, Object b)
    {
        System.Reflection.FieldInfo[] fields = a.GetType().GetFields(); // get all the properties of object a

        foreach (var field in fields)
        {
            var fieldName = field.Name;

            var aValue = a.GetType().GetField(fieldName).GetValue(a);

            object bValue;

            try // try to get the same property from object b. maybe that property does
            // not exist! 
            {
                bValue = b.GetType().GetField(fieldName).GetValue(b);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
               continue;

            if (aValue == null && bValue != null)
               return false;

            if (aValue != null && bValue == null)
               return false;


            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }


}
Tono Nam
quelle
Dieser Code ist nicht 100% effizient. In einigen Situationen funktioniert es beispielsweise nicht, wenn es eine Eigenschaft vom Typ Objekt enthält.
Tono Nam
0

Update zu Livius Antwort oben - CompareObjects.DifferencesString ist veraltet.

Dies funktioniert gut in einem Unit-Test:

CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(object1, object2);
Assert.IsTrue(result.AreEqual);
Daniel de Zwaan
quelle
1
Es ist großartig, dass Sie die Depracation behoben haben, aber ich denke, diese Antwort sollte eigentlich ein Kommentar in der Antwort von Liviu sein. Insbesondere, weil Ihrem Beispielcode (im Vergleich zu Livius ) die Parameter von CompareLogic (die sicher wichtig sind) und auch die Bestätigungsnachricht (die veraltet war ) fehlen . Die Behauptung kann behoben werden mit:Assert.IsTrue(result.AreEqual, result.DifferencesString);
Mariano Desanze
0

Diese Methode wird propertiesaus der Klasse abgerufen und die Werte für jede Klasse verglichen property. Wenn einer der Werte unterschiedlich ist, wird es return false, sonst wird es return true.

public static bool Compare<T>(T Object1, T object2)
{
    //Get the type of the object
    Type type = typeof(T);

    //return false if any of the object is false
    if (Object1 == null || object2 == null)
        return false;

    //Loop through each properties inside class and get values for the property from both the objects and compare
    foreach (System.Reflection.PropertyInfo property in type.GetProperties())
    {
        if (property.Name != "ExtensionData")
        {
            string Object1Value = string.Empty;
            string Object2Value = string.Empty;
            if (type.GetProperty(property.Name).GetValue(Object1, null) != null)
                Object1Value = type.GetProperty(property.Name).GetValue(Object1, null).ToString();
            if (type.GetProperty(property.Name).GetValue(object2, null) != null)
                Object2Value = type.GetProperty(property.Name).GetValue(object2, null).ToString();
            if (Object1Value.Trim() != Object2Value.Trim())
            {
                return false;
            }
        }
    }
    return true;
}

Verwendung:

bool isEqual = Compare<Employee>(Object1, Object2)

Absender
quelle
0

Um die Antwort von @nawfal: s zu erweitern, verwende ich diese Option, um Objekte verschiedener Typen in meinen Komponententests zu testen und gleiche Eigenschaftsnamen zu vergleichen. In meinem Fall Datenbankentität und DTO.

Wird in meinem Test so verwendet.

Assert.IsTrue(resultDto.PublicInstancePropertiesEqual(expectedEntity));



public static bool PublicInstancePropertiesEqual<T, Z>(this T self, Z to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var type2 = typeof(Z);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
           from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
           where !ignoreList.Contains(pi.Name)
           let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
           let toValue = type2.GetProperty(pi.Name).GetValue(to, null)
           where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
           select selfValue;
           return !unequalProperties.Any();
    }
    return self == null && to == null;
}
Sgedda
quelle
0

Manchmal möchten Sie nicht alle öffentlichen Eigenschaften vergleichen und nur die Teilmenge von ihnen vergleichen. In diesem Fall können Sie also einfach die Logik verschieben, um die gewünschte Liste von Eigenschaften mit der abstrakten Klasse zu vergleichen

public abstract class ValueObject<T> where T : ValueObject<T>
{
    protected abstract IEnumerable<object> GetAttributesToIncludeInEqualityCheck();

    public override bool Equals(object other)
    {
        return Equals(other as T);
    }

    public bool Equals(T other)
    {
        if (other == null)
        {
            return false;
        }

        return GetAttributesToIncludeInEqualityCheck()
            .SequenceEqual(other.GetAttributesToIncludeInEqualityCheck());
    }

    public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
    {
        return !(left == right);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        foreach (var obj in this.GetAttributesToIncludeInEqualityCheck())
            hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());

        return hash;
    }
}

und verwenden Sie diese abstrakte Klasse später, um die Objekte zu vergleichen

public class Meters : ValueObject<Meters>
{
    ...

    protected decimal DistanceInMeters { get; private set; }

    ...

    protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
    {
        return new List<Object> { DistanceInMeters };
    }
}
Mr. Pumpkin
quelle
0

Meine Lösung wurde von der obigen Antwort von Aras Alenin inspiriert, in der ich eine Ebene des Objektvergleichs und ein benutzerdefiniertes Objekt für Vergleichsergebnisse hinzugefügt habe. Ich bin auch daran interessiert, den Eigenschaftsnamen mit dem Objektnamen zu erhalten:

    public static IEnumerable<ObjectPropertyChanged> GetPublicSimplePropertiesChanged<T>(this T previous, T proposedChange,
     string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, true, null, null);
    }

    public static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, false, null, null);
    }

    /// <summary>
    /// Gets the names of the public properties which values differs between first and second objects.
    /// Considers 'simple' properties AND for complex properties without index, get the simple properties of the children objects.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="previous">The previous object.</param>
    /// <param name="proposedChange">The second object which should be the new one.</param>
    /// <param name="namesOfPropertiesToBeIgnored">The names of the properties to be ignored.</param>
    /// <param name="simpleTypeOnly">if set to <c>true</c> consider simple types only.</param>
    /// <param name="parentTypeString">The parent type string. Meant only for recursive call with simpleTypeOnly set to <c>true</c>.</param>
    /// <param name="secondType">when calling recursively, the current type of T must be clearly defined here, as T will be more generic (using base class).</param>
    /// <returns>
    /// the names of the properties
    /// </returns>
    private static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored, bool simpleTypeOnly, string parentTypeString, Type secondType) where T : class
    {
        List<ObjectPropertyChanged> propertiesChanged = new List<ObjectPropertyChanged>();

        if (previous != null && proposedChange != null)
        {
            var type = secondType == null ? typeof(T) : secondType;
            string typeStr = parentTypeString + type.Name + ".";
            var ignoreList = namesOfPropertiesToBeIgnored.CreateList();
            IEnumerable<IEnumerable<ObjectPropertyChanged>> genericPropertiesChanged =
                from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                where !ignoreList.Contains(pi.Name) && pi.GetIndexParameters().Length == 0 
                    && (!simpleTypeOnly || simpleTypeOnly && pi.PropertyType.IsSimpleType())
                let firstValue = type.GetProperty(pi.Name).GetValue(previous, null)
                let secondValue = type.GetProperty(pi.Name).GetValue(proposedChange, null)
                where firstValue != secondValue && (firstValue == null || !firstValue.Equals(secondValue))
                let subPropertiesChanged = simpleTypeOnly || pi.PropertyType.IsSimpleType()
                    ? null
                    : GetPublicGenericPropertiesChanged(firstValue, secondValue, namesOfPropertiesToBeIgnored, true, typeStr, pi.PropertyType)
                let objectPropertiesChanged = subPropertiesChanged != null && subPropertiesChanged.Count() > 0
                    ? subPropertiesChanged
                    : (new ObjectPropertyChanged(proposedChange.ToString(), typeStr + pi.Name, firstValue.ToStringOrNull(), secondValue.ToStringOrNull())).CreateList()
                select objectPropertiesChanged;

            if (genericPropertiesChanged != null)
            {   // get items from sub lists
                genericPropertiesChanged.ForEach(a => propertiesChanged.AddRange(a));
            }
        }
        return propertiesChanged;
    }

Verwenden der folgenden Klasse zum Speichern von Vergleichsergebnissen

[System.Serializable]
public class ObjectPropertyChanged
{
    public ObjectPropertyChanged(string objectId, string propertyName, string previousValue, string changedValue)
    {
        ObjectId = objectId;
        PropertyName = propertyName;
        PreviousValue = previousValue;
        ProposedChangedValue = changedValue;
    }

    public string ObjectId { get; set; }

    public string PropertyName { get; set; }

    public string PreviousValue { get; set; }

    public string ProposedChangedValue { get; set; }
}

Und ein Beispiel-Unit-Test:

    [TestMethod()]
    public void GetPublicGenericPropertiesChangedTest1()
    {
        // Define objects to test
        Function func1 = new Function { Id = 1, Description = "func1" };
        Function func2 = new Function { Id = 2, Description = "func2" };
        FunctionAssignment funcAss1 = new FunctionAssignment
        {
            Function = func1,
            Level = 1
        };
        FunctionAssignment funcAss2 = new FunctionAssignment
        {
            Function = func2,
            Level = 2
        };

        // Main test: read properties changed
        var propertiesChanged = Utils.GetPublicGenericPropertiesChanged(funcAss1, funcAss2, null);

        Assert.IsNotNull(propertiesChanged);
        Assert.IsTrue(propertiesChanged.Count == 3);
        Assert.IsTrue(propertiesChanged[0].PropertyName == "FunctionAssignment.Function.Description");
        Assert.IsTrue(propertiesChanged[1].PropertyName == "FunctionAssignment.Function.Id");
        Assert.IsTrue(propertiesChanged[2].PropertyName == "FunctionAssignment.Level");
    }
EricBDev
quelle