Comparación de propiedades de objeto en c # [cerrado]

111

Esto es lo que se me ocurrió como método en una clase heredada por muchas de mis otras clases. La idea es que permita la comparación simple entre propiedades de Objetos del mismo Tipo.

Ahora, esto funciona, pero en aras de mejorar la calidad de mi código, pensé en tirarlo para su escrutinio. ¿Cómo puede ser mejor / más eficiente / etc.?

/// <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
fuente
3
Por cierto que son conscientes de este sitio SE: codereview.stackexchange.com
WIP
Hay algunas bibliotecas de comparación de objetos: CompareNETObjects , DeepEqual , AutoCompare , ZCompare ...
nawfal
... y una tonelada de implementadores de comparación de igualdad genéricos, algunos de los cuales son: MemberwiseEqualityComparer , Equ , SemanticComparison , EqualityComparer , DeepEqualityComparer , Equality , Equals.Fody . Este último grupo podría tener un alcance y una flexibilidad limitados en cuanto a lo que pueden lograr.
Nawfal
Estoy votando para cerrar esta pregunta como fuera de tema porque pertenece a la revisión del código
Xiaoy312

Respuestas:

160

Estaba buscando un fragmento de código que hiciera algo similar para ayudar con la escritura de la prueba unitaria. Esto es lo que terminé usando.

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

EDITAR:

El mismo código que el anterior, pero utiliza los métodos LINQ y 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
fuente
Big T: bastante viejo, pero definitivamente tiene un gran propósito tanto para las pruebas como para las comparaciones simples ... gracias +1
jim tollan
1
Esto es bueno, pero he descubierto que no funciona con objetos más complejos. Por ejemplo, tengo un objeto con algunas cadenas (las compara bien) pero este objeto también tiene una lista de otro objeto, que no se compara correctamente, así que necesito repetir esto de alguna manera.
Ryan Thomas
1
Tuve que agregar a los criterios en el primero donde dos criterios más porque tiene que excluir las propiedades indexadas que arrojan una excepción en el otro caso. Aquí están los criterios para este error: pi.GetIndexParameters (). Length == 0. Y el segundo criterio para resolver el problema indicado por @RyanThomas es este: pi.GetUnderlyingType (). IsSimpleType (). Como verá, IsSimpleType es una extensión que no existe para la clase Type. Modifiqué la respuesta para agregar todas estas condiciones y la extensión.
Samuel
64

ACTUALIZACIÓN: La última versión de Compare-Net-Objects se encuentra en GitHub , tiene el paquete NuGet y el Tutorial . Se puede llamar como

//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 si necesita cambiar alguna configuración, use

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

La lista completa de parámetros configurables está en ComparisonConfig.cs

Respuesta original:

Las limitaciones que veo en tu código:

  • El más importante es que no hace una comparación de objetos profunda.

  • No hace una comparación elemento por elemento en caso de que las propiedades sean listas o contengan listas como elementos (esto puede ir en n niveles).

  • No tiene en cuenta que algún tipo de propiedades no deben ser comparadas (por ejemplo, una propiedad Func utilizada con fines de filtrado, como la de la clase PagedCollectionView).

  • No realiza un seguimiento de las propiedades que realmente eran diferentes (para que pueda mostrarlas en sus afirmaciones).

Hoy estaba buscando alguna solución con fines de prueba unitaria para hacer una comparación profunda propiedad por propiedad y terminé usando: http://comparenetobjects.codeplex.com .

Es una biblioteca gratuita con una sola clase que puedes usar así:

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

Además, se puede volver a compilar fácilmente para Silverlight. Simplemente copie la clase en un proyecto de Silverlight y elimine una o dos líneas de código para las comparaciones que no están disponibles en Silverlight, como la comparación de miembros privados.

Liviu Trifoi
fuente
2
Liviu, noté que tu comentario acerca de que la clase no es compatible con Silverlight. Lo acabo de cambiar para que sea compatible con Silverlight y Windows Phone 7. Obtenga lo último. Consulte el conjunto de cambios 74131 en comparenetobjects.codeplex.com/SourceControl/list/changesets
Greg Finzer
Esto parece prometedor. Voy a probarlo
DJ Burb
¡Gracias por el gran ejemplo! Además, la IgnoreObjectTypesconfiguración puede ser útil cuando hay diferentes tipos.
Sergey Brunov
La versión 2.0 tiene una versión de biblioteca de clases portátil que es compatible con Silverlight 5+, Windows Phone 8+, WinRT 8+, Xamarin IOS y Xamarin Droid
Greg Finzer
DifferencesStringha quedado obsoleto en la clase CompareObjects. Pero ahora puede obtener eso del ComparisonResult en su lugar:var r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
Mariano Desanze
6

Creo que sería mejor seguir el patrón de Ignorar objeto # Igual ()
Para una mejor descripción: Lea el C # efectivo de Bill Wagner - Elemento 9, creo

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);
}
  • Además, en los métodos que comprueban la igualdad, debe devolver verdadero o falso. o son iguales o no lo son ... en lugar de lanzar una excepción, devuelve falso.
  • Consideraría anular Object # Equals.
  • Aunque debe haber considerado esto, usar Reflection para comparar propiedades es supuestamente lento (no tengo números para respaldar esto). Este es el comportamiento predeterminado para valueType # Equals en C # y se recomienda que anule Equals para los tipos de valor y realice una comparación inteligente de miembros para el rendimiento. (Anteriormente leí esto rápidamente, ya que tiene una colección de objetos de propiedad personalizados ... mi mal).

Actualización-diciembre de 2011:

  • Por supuesto, si el tipo ya tiene una producción Equals (), entonces necesita otro enfoque.
  • Si está utilizando esto para comparar estructuras de datos inmutables exclusivamente con fines de prueba, no debe agregar un Equals a las clases de producción (alguien podría manipular las pruebas encadenando la implementación Equals o puede evitar la creación de una implementación Equals requerida por la producción) .
Gishu
fuente
Me encontré con problemas al anular .Equals () porque estoy tratando de implementar esto en una clase base que se hereda ... porque no sé las claves para la clase contra la que se ejecutará, no puedo implemente una anulación decente para GetHasCode () (requerido cuando anula Equals ()).
nailitdown
El requisito es que si objA.Equals (objB) entonces objA.GetHashCode () == objB.GetHashCode (). GetHashCode no debería depender del estado / datos mutables de una clase ... No entendí lo que querías decir con claves para la clase ... Parece algo que se puede resolver. ¿No tiene el tipo base las 'claves'?
Gishu
6

Si el rendimiento no importa, puede serializarlos y comparar los resultados:

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
fuente
4
probé esto hace un rato, te preguntarías cuántos objetos no son serializables ...
Offler
5

Creo que la respuesta de Big T fue bastante buena, pero faltaba la comparación profunda, así que la modifiqué un poco:

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
fuente
4

Agregaría la siguiente línea al método PublicInstancePropertiesEqual para evitar errores de copiar y pegar:

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

¿Anula .ToString () en todos sus objetos que están en las propiedades? De lo contrario, esa segunda comparación podría volverse nula.

Además, en esa segunda comparación, estoy indeciso sobre la construcción de! (A == B) en comparación con (A! = B), en términos de legibilidad dentro de seis meses / dos años. La línea en sí es bastante ancha, lo cual está bien si tiene un monitor ancho, pero es posible que no se imprima muy bien. (quisquilloso)

¿Todos sus objetos siempre usan propiedades de modo que este código funcione? ¿Podría haber algunos datos internos no propietarios que podrían ser diferentes de un objeto a otro, pero todos los datos expuestos son iguales? Estoy pensando en algunos datos que podrían cambiar con el tiempo, como dos generadores de números aleatorios que llegan al mismo número en un punto, pero van a producir dos secuencias de información diferentes, o simplemente cualquier dato que no quede expuesto. a través de la interfaz de propiedad.

mmr
fuente
buenos puntos -! = ... de acuerdo, punto tomado. ToString () fue un intento de solucionar .GetValue devolviendo un objeto (por lo tanto, la comparación siempre es falsa, ya que es una comparación de referencia) ... ¿hay una mejor manera?
nailitdown
Si GetValue devuelve un objeto, ¿puede volver a recurrir a esta función? es decir, llamar a PropertiesEqual en los objetos devueltos?
mmr
1

Si solo está comparando objetos del mismo tipo o más abajo en la cadena de herencia, ¿por qué no especificar el parámetro como su tipo base, en lugar de como objeto?

También realice comprobaciones nulas en el parámetro.

Además, haría uso de 'var' solo para hacer que el código sea más legible (si es el código c # 3)

Además, si el objeto tiene tipos de referencia como propiedades, entonces solo está llamando a ToString () sobre ellos, lo que realmente no compara valores. Si ToString no se sobrescribe, solo devolverá el nombre del tipo como una cadena que podría devolver falsos positivos.

Pato Darkwing
fuente
Buen punto sobre los tipos de referencia: en mi caso, no importa, pero hay muchas posibilidades de que así sea.
nailitdown
1

Lo primero que sugeriría sería dividir la comparación real para que sea un poco más legible (también he sacado el ToString (), ¿es necesario?):

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

    if (originalProperty != comparisonProperty)
        return false;

La siguiente sugerencia sería minimizar el uso de la reflexión tanto como sea posible; es realmente lento. Quiero decir, muy lento. Si va a hacer esto, le sugiero que almacene en caché las referencias de propiedad. No estoy muy familiarizado con la API de Reflection, así que si esto está un poco apagado, simplemente ajústelo para que se compile:

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

Sin embargo, debo decir que estoy de acuerdo con los demás carteles. Esto huele a perezoso e ineficaz. En su lugar, debería implementar IComparable :-).

tsimon
fuente
Solo estaba mirando IComparable pero parecía que era para clasificar y ordenar ... ¿es realmente útil para comparar la igualdad de dos objetos?
nailitdown
Absolutamente, porque .Equals (objeto o) se define como this.CompareTo (o) == 0. Entonces, equals usa ComparesTo () para determinar la igualdad. Esto será mucho más eficiente (y práctica estándar) que usar la reflexión.
tsimon
Puedo estar equivocado al suponer que Equals está implementado (o debería implementarse) con referencia a CompareTo (). Debería considerar anular Equals como se describe aquí: stackoverflow.com/questions/104158/…
tsimon
1

aquí se revisa uno para tratar nulo = nulo como igual

 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
fuente
¿Qué pasaría si tuviera un gráfico de objetos profundo, cuál es la mejor manera de usar arriba para devolver una lista de propiedades antiguas y nuevas que se cambiaron?
Rod
1

Terminé haciendo esto:

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

Actualizar

Si desea ignorar algunas propiedades por nombre:

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

Puede optimizar su código llamando a GetProperties solo una vez por 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;
}
Moti Elbilya
fuente
1

Para completar, quiero agregar una referencia a http://www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflection Tiene una lógica más completa que la mayoría de las otras respuestas en esta página.

Sin embargo, prefiero la biblioteca Compare-Net-Objects https://github.com/GregFinzer/Compare-Net-Objects (referido por la respuesta de Liviu Trifoi ) La biblioteca tiene el paquete NuGet http://www.nuget.org/packages/ CompareNETObjects y múltiples opciones para configurar.

Michael Freidgeim
fuente
1

Asegúrese de que los objetos no lo sean null.

Tener obj1y obj2:

if(obj1 == null )
{
   return false;
}
return obj1.Equals( obj2 );
Ric Tokio
fuente
¿y si ambos son nulos? ¿No son entonces iguales?
mmr
buen punto en nulos, en mi caso, el uso de .Equals () no parece funcionar, por lo que se me ocurrió esta solución
nailitdown
Bueno, el caso que estoy probando es de dos objetos, uno recién creado y otro de la sesión. comparar los dos con .Equals () devuelve falso a pesar de que ambos tienen valores de propiedad idénticos
nailitdown
0

Esto funciona incluso si los objetos son diferentes. podría personalizar los métodos en la clase de utilidades, tal vez también desee comparar propiedades privadas ...

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
fuente
Ese código no es 100% eficiente. no funciona en algunas situaciones, por ejemplo, si contiene una propiedad de tipo objeto.
Tono Nam
0

Actualización sobre la respuesta de Liviu anterior: CompareObjects.DifferencesString ha quedado obsoleto.

Esto funciona bien en una prueba unitaria:

CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(object1, object2);
Assert.IsTrue(result.AreEqual);
Daniel de Zwaan
fuente
1
Es genial que hayas arreglado la depravación, pero creo que esta respuesta debería ser un comentario en la respuesta de Liviu. Especialmente porque su código de muestra (comparado con el de Liviu) carece de los parámetros de CompareLogic (que estoy seguro que son importantes), y también del mensaje de aserción (que era el obsoleto). La afirmación se puede arreglar con:Assert.IsTrue(result.AreEqual, result.DifferencesString);
Mariano Desanze
0

Este método obtendrá propertiesde la clase y comparará los valores de cada uno property. Si alguno de los valores es diferente, lo será return false, de lo contrario lo será 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)

Remitente
fuente
0

Para ampliar la respuesta de @nawfal: s, lo uso para probar objetos de diferentes tipos en mis pruebas unitarias para comparar nombres de propiedades iguales. En mi caso, entidad de base de datos y DTO.

Usado así en mi prueba;

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
fuente
0

a veces no desea comparar todas las propiedades públicas y desea comparar solo el subconjunto de ellas, por lo que en este caso puede mover la lógica para comparar la lista deseada de propiedades con la clase abstracta

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

y usa esta clase abstracta más tarde para comparar los objetos

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

    protected decimal DistanceInMeters { get; private set; }

    ...

    protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
    {
        return new List<Object> { DistanceInMeters };
    }
}
Sr. Calabaza
fuente
0

mi solución inspirada en la respuesta de Aras Alenin anterior, donde agregué un nivel de comparación de objetos y un objeto personalizado para obtener resultados de comparación. También estoy interesado en obtener el nombre de la propiedad con el nombre del objeto:

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

Usando la siguiente clase para almacenar resultados de comparación

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

Y una prueba unitaria de muestra:

    [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
fuente