Confronto delle proprietà degli oggetti in c # [chiuso]


111

Questo è ciò che ho escogitato come metodo su una classe ereditata da molte delle mie altre classi. L'idea è che consente il semplice confronto tra proprietà di oggetti dello stesso tipo.

Ora, questo funziona, ma nell'interesse di migliorare la qualità del mio codice ho pensato di buttarlo fuori per un controllo. Come può essere migliore / più efficiente / ecc.?

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


3
A proposito, sei a conoscenza di questo sito SE: codereview.stackexchange.com
wip

Ci sono alcune librerie di confronto oggetto: CompareNETObjects , DeepEqual , AutoCompare , ZCompare ...
Nawfal

... e una tonnellata di implementatori generici di comparatori di uguaglianza, alcuni dei quali sono: MemberwiseEqualityComparer , Equ , SemanticComparison , EqualityComparer , DeepEqualityComparer , Equality , Equals.Fody . Quest'ultimo gruppo potrebbe essere limitato in termini di portata e flessibilità su ciò che possono ottenere.
nawfal

Voto

Risposte:


160

Stavo cercando uno snippet di codice che potesse fare qualcosa di simile per aiutare con la scrittura di unit test. Ecco cosa ho finito per usare.

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

MODIFICARE:

Stesso codice di cui sopra ma utilizza i metodi LINQ e 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"
               );
         }
      }
   }

Big T - piuttosto un vecchio, ma sicuramente ha un ottimo scopo sia per i test che per i semplici confronti .. grazie +1
jim tollan

1
Questo va bene, ma ho scoperto che non funziona con oggetti più complessi. Ad esempio, ho un oggetto con alcune stringhe (le confronta bene) ma poi questo oggetto ha anche un elenco di un altro oggetto, che non confronta correttamente, quindi è necessario ricorrere in qualche modo.
Ryan Thomas,

1
Ho dovuto aggiungere ai criteri nel primo dove altri due criteri perché devi escludere le proprietà indicizzate che generano un'eccezione in altri casi. Ecco i criteri per questo errore: pi.GetIndexParameters (). Length == 0. E il secondo criterio per risolvere il problema indicato da @RyanThomas è questo: pi.GetUnderlyingType (). IsSimpleType (). Come vedrai, IsSimpleType è un'estensione che non esiste per la classe Type. Ho modificato la risposta per aggiungere tutte queste condizioni e l'estensione.
Samuel,

64

AGGIORNAMENTO: l'ultima versione di Compare-Net-Objects si trova su GitHub , ha il pacchetto NuGet e Tutorial . Può essere chiamato come

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

O se hai bisogno di cambiare qualche configurazione, usa

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

L'elenco completo dei parametri configurabili è in ComparisonConfig.cs

Risposta originale:

I limiti che vedo nel tuo codice:

  • Il più grande è che non esegue un confronto profondo degli oggetti.

  • Non esegue un confronto elemento per elemento nel caso in cui le proprietà siano elenchi o contengano elenchi come elementi (questo può andare su n livelli).

  • Non tiene conto del fatto che alcuni tipi di proprietà non dovrebbero essere confrontati (ad esempio una proprietà Func usata per scopi di filtraggio, come quella nella classe PagedCollectionView).

  • Non tiene traccia di quali proprietà fossero effettivamente diverse (quindi puoi mostrarle nelle tue affermazioni).

Oggi stavo cercando una soluzione a scopo di test unitario per eseguire un confronto approfondito della proprietà per proprietà e ho finito per utilizzare: http://comparenetobjects.codeplex.com .

È una libreria gratuita con una sola classe che puoi usare semplicemente in questo modo:

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

Inoltre, può essere facilmente ricompilato per Silverlight. È sufficiente copiare una classe in un progetto Silverlight e rimuovere una o due righe di codice per i confronti che non sono disponibili in Silverlight, come il confronto dei membri privati.


2
Liviu, ho notato il tuo commento sul fatto che la classe non è compatibile con Silverlight. L'ho appena cambiato per renderlo compatibile con Silverlight e Windows Phone 7. Procurati l'ultima versione. Vedere la serie di modifiche 74131 su comparenetobjects.codeplex.com/SourceControl/list/changesets
Greg Finzer

Sembra promettente. Lo proverò
DJ Burb

Grazie per l'ottimo esempio! Inoltre, l' IgnoreObjectTypesimpostazione potrebbe essere utile quando ci sono diversi tipi.
Sergey Brunov

La versione 2.0 ha una versione Portable Class Library compatibile con Silverlight 5+, Windows Phone 8+, WinRT 8+, Xamarin IOS e Xamarin Droid
Greg Finzer

DifferencesStringè stato sconsigliato nella classe CompareObjects. Ma ora puoi ottenerlo dal ComparisonResult invece:var r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
Mariano Desanze

6

Penso che sarebbe meglio seguire lo schema per Override Object # Equals ()
Per una descrizione migliore: Leggi Effective C # di Bill Wagner - Item 9 Penso

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);
}
  • Anche nei metodi che verificano l'uguaglianza, è necessario restituire true o false. o sono uguali o non lo sono .. invece di lanciare un'eccezione, restituisci false.
  • Considererei l'override di Object # Equals.
  • Anche se devi aver considerato questo, l'uso di Reflection per confrontare le proprietà è presumibilmente lento (non ho numeri per supportarlo). Questo è il comportamento predefinito per valueType # Equals in C # ed è consigliabile eseguire l'override di Equals per i tipi di valore e fare un confronto saggio dei membri per le prestazioni. (In precedenza ho letto questo articolo perché hai una raccolta di oggetti Property personalizzati ... colpa mia.)

Aggiornamento-dicembre 2011:

  • Ovviamente, se il tipo ha già una produzione Equals (), è necessario un altro approccio.
  • Se lo stai utilizzando per confrontare strutture di dati immutabili esclusivamente a scopo di test, non dovresti aggiungere un Equals alle classi di produzione (qualcuno potrebbe bloccare i test inseguendo l'implementazione Equals o potresti impedire la creazione di un'implementazione Equals richiesta dalla produzione) .

Ho riscontrato problemi con l'override di .Equals () perché sto cercando di implementarlo su una classe base che viene ereditata ... perché non conosco le chiavi per la classe su cui verrà eseguito, non posso implementare un decente override per GetHasCode () (richiesto quando si sovrascrive Equals ()).
nailitdown

Il requisito è che se objA.Equals (objB) allora objA.GetHashCode () == objB.GetHashCode (). GetHashCode non dovrebbe dipendere dallo stato / dati modificabili di una classe ... Non ho capito cosa intendevi per chiavi per la classe .. Sembra qualcosa che può essere risolto. Il tipo di base non ha le "chiavi"?
Gishu

6

Se le prestazioni non contano, puoi serializzarle e confrontare i risultati:

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

4
provato questo un whila fa, ti chiederesti quanti oggetti non sono serializzabili ...
Offler

5

Penso che la risposta di Big T fosse abbastanza buona ma mancava il confronto profondo, quindi l'ho ottimizzato un po ':

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

4

Aggiungerei la seguente riga al metodo PublicInstancePropertiesEqual per evitare errori di copia e incolla:

Assert.AreNotSame(self, to);

2

Esegui l'override di .ToString () su tutti i tuoi oggetti che sono nelle proprietà? Altrimenti, quel secondo confronto potrebbe tornare con null.

Inoltre, in quel secondo confronto, sono indifferente al costrutto di! (A == B) rispetto a (A! = B), in termini di leggibilità tra sei mesi / due anni. La linea stessa è piuttosto ampia, il che va bene se hai un monitor ampio, ma potrebbe non essere stampata molto bene. (Nitpick)

Tutti i tuoi oggetti utilizzano sempre proprietà in modo tale che questo codice funzioni? Potrebbero esserci alcuni dati interni non di proprietà che potrebbero essere diversi da un oggetto all'altro, ma tutti i dati esposti sono gli stessi? Sto pensando ad alcuni dati che potrebbero cambiare nel tempo, come due generatori di numeri casuali che capita di colpire lo stesso numero in un punto, ma produrranno due diverse sequenze di informazioni, o semplicemente qualsiasi dato che non viene esposto tramite l'interfaccia delle proprietà.


buoni punti -! = ... d'accordo, punto preso. ToString () è stato un tentativo di soluzione alternativa .GetValue restituendo un oggetto (quindi il confronto è sempre falso, poiché è un confronto di riferimento) .. c'è un modo migliore?
nailitdown

Se GetValue restituisce un oggetto, puoi ricorrere di nuovo tramite questa funzione? cioè, chiamare PropertiesEqual sugli oggetti restituiti?
mmr

1

Se stai confrontando solo oggetti dello stesso tipo o più in basso nella catena di ereditarietà, perché non specificare il parametro come tipo di base, anziché come oggetto?

Esegui anche controlli nulli sul parametro.

Inoltre, utilizzerei "var" solo per rendere il codice più leggibile (se è codice c # 3)

Inoltre, se l'oggetto ha tipi di riferimento come proprietà, stai semplicemente chiamando ToString () su di essi che non confronta i valori. Se ToString non viene sovrascritto, restituirà il nome del tipo come una stringa che potrebbe restituire falsi positivi.


buon punto sui tipi di riferimento - nel mio caso non importa, ma ci sono buone probabilità che lo sia.
nailitdown

1

La prima cosa che suggerirei sarebbe di suddividere il confronto effettivo in modo che sia un po 'più leggibile (ho anche tolto ToString () - è necessario?):

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

    if (originalProperty != comparisonProperty)
        return false;

Il prossimo suggerimento sarebbe quello di ridurre al minimo l'uso della riflessione il più possibile: è molto lento. Voglio dire, molto lento. Se hai intenzione di farlo, ti suggerisco di memorizzare nella cache i riferimenti alle proprietà. Non ho familiarità con l'API Reflection, quindi se questo è un po 'fuori posto, aggiusta per farlo compilare:

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

Tuttavia, devo dire che sono d'accordo con gli altri poster. Questo ha un odore pigro e inefficiente. Dovresti invece implementare IComparable :-).


Stavo solo guardando IComparable ma sembrava che fosse per l'ordinamento e l'ordinamento .. è davvero utile per confrontare l'uguaglianza di due oggetti?
nailitdown

Assolutamente, perché .Equals (oggetto o) è definito come this.CompareTo (o) == 0. Quindi, equals usa ComparesTo () per determinare l'uguaglianza. Questo sarà molto più efficiente (e pratica standard) rispetto all'uso della riflessione.
tsimon

Potrei sbagliarmi supponendo che Equals sia implementato (o debba essere implementato) con riferimento a CompareTo (). Si dovrebbe prendere in considerazione l'override Equals come descritto qui: stackoverflow.com/questions/104158/...
tsimon

1

qui viene rivisto uno per considerare null = null come uguale

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

E se avessi un grafico a oggetti profondo, qual è il modo migliore per utilizzare sopra per restituire un elenco di proprietà vecchie e nuove che sono state modificate?
Rod

1

Ho finito per fare questo:

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

Uso:

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

Aggiornare

Se vuoi ignorare alcune proprietà per nome:

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

Uso:

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

1

Puoi ottimizzare il tuo codice chiamando GetProperties solo una volta per tipo:

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

Per completezza voglio aggiungere un riferimento a http://www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflection Ha una logica più completa rispetto alla maggior parte delle altre risposte in questa pagina.

Comunque io preferisco compare-Net-Objects Library https://github.com/GregFinzer/Compare-Net-Objects (di cui da Liviu Trifoi 's risposta )
La biblioteca ha pacchetto NuGet http://www.nuget.org/packages/ CompareNETObjects e più opzioni da configurare.


1

Assicurati che gli oggetti non lo siano null.

Avere obj1e obj2:

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

e se fossero entrambi nulli? non sono quindi uguali?
mmr

buon punto sui valori nulli, nel mio caso l'utilizzo di .Equals () non sembra funzionare, motivo per cui ho escogitato questa soluzione
nailitdown

beh, il caso che sto testando è di due oggetti, uno appena creato, uno dalla sessione. il confronto dei due con .Equals () restituisce false anche se entrambi hanno valori di proprietà identici
nailitdown

0

Funziona anche se gli oggetti sono diversi. potresti personalizzare i metodi nella classe delle utilità forse vuoi confrontare anche le proprietà private ...

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


}

Quel codice non è efficiente al 100%. non funziona in alcune situazioni, ad esempio se contiene una proprietà di tipo oggetto.
Tono Nam

0

Aggiornamento sulla risposta di Liviu sopra: CompareObjects.DifferencesString è stato deprecato.

Funziona bene in uno unit test:

CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(object1, object2);
Assert.IsTrue(result.AreEqual);

1
È fantastico che tu abbia risolto il problema, ma penso che questa risposta dovrebbe in realtà essere un commento nella risposta di Liviu. Soprattutto perché il tuo codice di esempio (rispetto a quello di Liviu) manca dei parametri di CompareLogic (che sono sicuro sono importanti) e anche del messaggio di asserzione (che era quello deprecato). L'asserzione può essere risolta con:Assert.IsTrue(result.AreEqual, result.DifferencesString);
Mariano Desanze

0

Questo metodo otterrà propertiesla classe e confronterà i valori per ciascuno property. Se uno qualsiasi dei valori è diverso, lo sarà return false, altrimenti lo sarà 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;
}

Uso:

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


0

Per espandere la risposta di @nawfal: s, lo uso per testare oggetti di tipi diversi nei miei test unitari per confrontare nomi di proprietà uguali. Nel mio caso, entità di database e DTO.

Usato in questo modo nel mio test;

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

a volte non vuoi confrontare tutte le proprietà pubbliche e vuoi confrontare solo il loro sottoinsieme, quindi in questo caso puoi semplicemente spostare la logica per confrontare l'elenco di proprietà desiderato con la classe astratta

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

e usa questa classe astratta in seguito per confrontare gli oggetti

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

    protected decimal DistanceInMeters { get; private set; }

    ...

    protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
    {
        return new List<Object> { DistanceInMeters };
    }
}

0

la mia soluzione si ispira alla risposta di Aras Alenin sopra, dove ho aggiunto un livello di confronto degli oggetti e un oggetto personalizzato per i risultati del confronto. Sono anche interessato a ottenere il nome della proprietà con il nome dell'oggetto:

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

Utilizzo della seguente classe per memorizzare i risultati del confronto

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

E un test unitario di esempio:

    [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");
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.