Pregunta La mejor forma de comparar dos objetos complejos


Tengo dos objetos complejos como Object1 y Object2. Tienen alrededor de 5 niveles de objetos secundarios.

Necesito el método más rápido para decir si son iguales o no.

¿Cómo podría hacerse esto en C # 4.0?


73
2018-05-04 18:50


origen


Respuestas:


Implementar IEquatable<T> (Por lo general, junto con anular el heredado Object.Equals y Object.GetHashCode métodos) en todos sus tipos personalizados. En el caso de los tipos compuestos, invoque los tipos contenidos ' Equals método dentro de los tipos que contienen. Para colecciones contenidas, use el SequenceEqual método de extensión, que internamente llama IEquatable<T>.Equals o Object.Equals en cada elemento. Este enfoque obviamente requerirá que amplíe las definiciones de sus tipos, pero sus resultados son más rápidos que cualquier solución genérica que involucre la serialización.

Editar: Aquí hay un ejemplo artificial con tres niveles de anidación.

Para los tipos de valor, normalmente puede simplemente llamar a su Equals método. Incluso si los campos o las propiedades nunca se asignaron explícitamente, aún tendrían un valor predeterminado.

Para los tipos de referencia, primero debe llamar ReferenceEquals, que verifica la igualdad de referencia; esto serviría como un refuerzo de la eficiencia cuando esté haciendo referencia al mismo objeto. También manejaría casos donde ambas referencias son nulas. Si esa verificación falla, confirme que el campo o propiedad de su instancia no es nulo (para evitar NullReferenceException) y llame a su Equals método. Dado que nuestros miembros están tipeados correctamente, el IEquatable<T>.Equals método se llama directamente, evitando el reemplazado Object.Equals método (cuya ejecución sería un poco más lenta debido al tipo de conversión).

Cuando anulas Object.Equals, también se espera que anule Object.GetHashCode; No lo hice a continuación en aras de la concisión.

public class Person : IEquatable<Person>
{
    public int Age { get; set; }
    public string FirstName { get; set; }
    public Address Address { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as Person);
    }

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

        return this.Age.Equals(other.Age) &&
            (
                object.ReferenceEquals(this.FirstName, other.FirstName) ||
                this.FirstName != null &&
                this.FirstName.Equals(other.FirstName)
            ) &&
            (
                object.ReferenceEquals(this.Address, other.Address) ||
                this.Address != null &&
                this.Address.Equals(other.Address)
            );
    }
}

public class Address : IEquatable<Address>
{
    public int HouseNo { get; set; }
    public string Street { get; set; }
    public City City { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as Address);
    }

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

        return this.HouseNo.Equals(other.HouseNo) &&
            (
                object.ReferenceEquals(this.Street, other.Street) ||
                this.Street != null &&
                this.Street.Equals(other.Street)
            ) &&
            (
                object.ReferenceEquals(this.City, other.City) ||
                this.City != null &&
                this.City.Equals(other.City)
            );
    }
}

public class City : IEquatable<City>
{
    public string Name { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as City);
    }

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

        return
            object.ReferenceEquals(this.Name, other.Name) ||
            this.Name != null &&
            this.Name.Equals(other.Name);
    }
}

75
2018-05-04 18:53



Serializar ambos objetos y comparar las cadenas resultantes


53
2017-10-13 21:51



Puede usar el método de extensión, recursión para resolver este problema:

public static bool DeepCompare(this object obj, object another)
{     
  if (ReferenceEquals(obj, another)) return true;
  if ((obj == null) || (another == null)) return false;
  //Compare two object's class, return false if they are difference
  if (obj.GetType() != another.GetType()) return false;

  var result = true;
  //Get all properties of obj
  //And compare each other
  foreach (var property in obj.GetType().GetProperties())
  {
      var objValue = property.GetValue(obj);
      var anotherValue = property.GetValue(another);
      if (!objValue.Equals(anotherValue)) result = false;
  }

  return result;
 }

public static bool CompareEx(this object obj, object another)
{
 if (ReferenceEquals(obj, another)) return true;
 if ((obj == null) || (another == null)) return false;
 if (obj.GetType() != another.GetType()) return false;

 //properties: int, double, DateTime, etc, not class
 if (!obj.GetType().IsClass) return obj.Equals(another);

 var result = true;
 foreach (var property in obj.GetType().GetProperties())
 {
    var objValue = property.GetValue(obj);
    var anotherValue = property.GetValue(another);
    //Recursion
    if (!objValue.DeepCompare(anotherValue))   result = false;
 }
 return result;
}

o compare usando Json (si el objeto es muy complejo) Puedes usar Newtonsoft.Json:

public static bool JsonCompare(this object obj, object another)
{
  if (ReferenceEquals(obj, another)) return true;
  if ((obj == null) || (another == null)) return false;
  if (obj.GetType() != another.GetType()) return false;

  var objJson = JsonConvert.SerializeObject(obj);
  var anotherJson = JsonConvert.SerializeObject(another);

  return objJson == anotherJson;
}

15
2018-03-21 07:27



Si no desea implementar IEquatable, siempre puede usar Reflection para comparar todas las propiedades: si son de tipo de valor, simplemente compárelas. Si son de tipo de referencia, llame a la función recursivamente para comparar sus propiedades "internas".

No estoy pensando en el rendimiento, sino en la simplicidad. Depende, sin embargo, del diseño exacto de tus objetos. Podría ser complicado dependiendo de la forma de su objeto. Sin embargo, hay varias soluciones que puedes usar, como esta:

No sé si por el más rápido te refieres a la manera más rápida de implementarlo o un código que corre rápido. No debe optimizar antes de saber si es necesario. La optimización temprana es la raíz de todo mal


15
2018-05-04 19:32



Serializar ambos objetos y comparar las cadenas resultantes por @JoelFan

Para hacer esto, crea una clase estática como esa y usa Extensiones para extender TODOS los objetos (para que puedas pasar cualquier tipo de objeto, colección, etc. al método)

using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;

public static class MySerializer
{
    public static string Serialize(this object obj)
    {
        var serializer = new DataContractJsonSerializer(obj.GetType());
        using (var ms = new MemoryStream())
        {
            serializer.WriteObject(ms, obj);
            return Encoding.Default.GetString(ms.ToArray());
        }
    }
}

Una vez que haga referencia a esta clase estática en cualquier otro archivo, puede hacer esto:

Person p = new Person { Firstname = "Jason", LastName = "Argonauts" };
Person p2 = new Person { Firstname = "Jason", LastName = "Argonaut" };
//assuming you have already created a class person!
string personString = p.Serialize();
string person2String = p2.Serialize();

Ahora puede simplemente usar .Equals para compararlos. Yo uso esto para verificar si los objetos están en colecciones también. Funciona realmente bien.


8
2018-01-20 11:27



Asumo que no te estás refiriendo literalmente a los mismos objetos

Object1 == Object2

Usted podría estar pensando en hacer una comparación de memoria entre los dos

memcmp(Object1, Object2, sizeof(Object.GetType())

Pero eso ni siquiera es código real en c # :). Debido a que todos sus datos probablemente se crean en el montón, la memoria no es contigua y no se puede simplemente comparar la igualdad de dos objetos de manera agnóstica. Tendrás que comparar cada valor, uno cada vez, de forma personalizada.

Considera agregar el IEquatable<T> interfaz a su clase, y defina una costumbre Equals método para su tipo. Luego, en ese método, prueba manualmente cada valor. Añadir IEquatable<T> nuevamente en los tipos adjuntos si puede y repita el proceso.

class Foo : IEquatable<Foo>
{
  public bool Equals(Foo other)
  {
    /* check all the values */
    return false;
  }
}

4
2018-05-04 18:53



Encontré esta función a continuación para comparar objetos.

 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 (object.Equals(Object1, default(T)) || object.Equals(object2, default(T)))
         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;
 }

Lo estoy usando y está funcionando bien para mí.


3
2018-05-20 11:46