webentwicklung-frage-antwort-db.com.de

Vergleichen von Objekteigenschaften in c #

Das habe ich mir als Methode für eine Klasse ausgedacht, 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 im Interesse der Verbesserung der Qualität meines Codes dachte ich, ich würde es zur Überprüfung rauswerfen. 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;
}
111
nailitdown

Ich war auf der Suche nach einem Codeausschnitt, der beim Schreiben von Komponententests eine ähnliche Funktion hat. 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;
  }

EDIT:

Gleicher Code wie oben, jedoch mit den 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"
               );
         }
      }
   }
160
Taras Alenin

UPDATE: Die neueste Version von Compare-Net-Objects befindet sich auf GitHub , hat NuGet-Paket = und Tutorial . Man kann es so nennen

//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 wenn Sie eine Konfiguration ändern müssen, verwenden Sie

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-stufig sein).

  • Es wird nicht berücksichtigt, dass einige 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-Testzwecke gesucht, um einen umfassenden Vergleich von Eigenschaften zu erstellen, und habe am Ende 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 ein oder zwei Codezeilen für Vergleiche, die in Silverlight nicht verfügbar sind, wie z. B. der Vergleich privater Mitglieder.

64
Liviu Trifoi

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();
6
Edward Brey

Ich denke, es wäre am besten, dem Muster für Override Object # Equals () zu folgen.
Zur besseren Beschreibung: Lies Bill Wagners Wirksam 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 sie sind nicht .. Anstatt eine Ausnahme auszulösen, geben Sie false zurück.
  • Ich würde in Betracht ziehen, Object # Equals zu überschreiben.
  • Obwohl Sie dies bedacht haben müssen, ist das Vergleichen von Eigenschaften mit Reflection vermutlich 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 Mitgliedervergleich für die Leistung durchzuführen. (Früher habe ich das schnell gelesen, da Sie eine Sammlung von benutzerdefinierten Property-Objekten haben ... meine schlechte.)

Update Dezember 2011:

  • Wenn der Typ bereits eine Produktion von Equals () hat, benötigen Sie natürlich einen anderen Ansatz.
  • Wenn Sie auf diese Weise unveränderliche Datenstrukturen ausschließlich zu Testzwecken vergleichen, sollten Sie den Produktionsklassen keine Equals hinzufügen. .
6
Gishu

Ich denke, die Antwort von Big T war ziemlich gut, aber der tiefe Vergleich fehlte, also habe ich es 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;
    }
}
5
Greg

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

Assert.AreNotSame(self, to);
4
thanei

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

Auch in diesem zweiten Vergleich bin ich auf dem Zaun, was das Konstrukt von! (A == B) im Vergleich zu (A! = B) betrifft, was die Lesbarkeit in sechs Monaten/zwei Jahren betrifft. 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 Daten ohne Eigenschaften geben, die sich von Objekt zu Objekt unterscheiden könnten, aber alle offen gelegten 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 die gleiche Zahl treffen, aber zwei verschiedene Informationssequenzen erzeugen, oder einfach Daten, die nicht offengelegt werden über die Eigenschaftsschnittstelle.

2
mmr

Der Vollständigkeit halber möchte ich noch einen Verweis hinzufügen auf http: //www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflection Es hat eine vollständigere Logik als Die meisten anderen antworten auf dieser Seite.

Ich bevorzuge jedoch Compare-Net-Objects library https: //github.com/GregFinzer/Compare-Net-Objects (verwiesen von Liviu Trifoi 's = antworte )
Die Bibliothek verfügt über das NuGet-Paket http: //www.nuget.org/packages/CompareNETObjects und mehrere zu konfigurierende Optionen.

1

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

Überprüfen Sie auch den Parameter auf Null.

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

Wenn das Objekt über Referenztypen als Eigenschaften verfügt, rufen Sie lediglich ToString () auf, wodurch die Werte nicht wirklich verglichen werden. Wenn ToString nicht überschrieben wird, wird nur der Typname als String zurückgegeben, der falsch-positive Ergebnisse liefern kann.

1
DarkwingDuck

Am Ende tat ich Folgendes:

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

pdate

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"))
1
BjarkeCK

Das erste, was ich vorschlagen würde, wäre, den tatsächlichen Vergleich so aufzuteilen, dass er ein bisschen 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, die Verwendung von Reflexion so weit wie möglich zu minimieren - es ist wirklich langsam. Ich meine, wirklich 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 es einfach an, damit es 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

Allerdings muss ich sagen, dass ich mit den anderen Plakaten einverstanden bin. Dies riecht faul und ineffizient. Sie sollten stattdessen IComparable implementieren :-).

1
tsimon

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;
}
1
Moti Elbilya

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;
        }
1
Hossein

Stellen Sie sicher, dass Objekte nicht null sind.

Mit obj1 und obj2:

if(obj1 == null )
{
   return false;
}
return obj1.Equals( obj2 );
1
Ric Tokyo

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 Objektnamen 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 Muster-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");
    }
0
EricBDev

manchmal möchten Sie nicht alle öffentlichen Eigenschaften und nur eine Teilmenge davon vergleichen. In diesem Fall können Sie die Logik einfach 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;
    }
}

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 };
    }
}
0
Mr. Pumpkin

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;
    }


}
0
Tono Nam

Um die Antwort von @nawfal zu erweitern, benutze 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;
}
0
Sgedda

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);
0
Daniel de Zwaan

Diese Methode ruft properties der Klasse ab und vergleicht die Werte für jedes property. Wenn einer der Werte unterschiedlich ist, wird er return false, Andernfalls wird er 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)

0
Sender