La mejor forma de comparar dos objetos complejos

112

Tengo dos objetos complejos como Object1y 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 se podría hacer esto en C # 4.0?

Desarrollador
fuente

Respuestas:

101

Implemente IEquatable<T>(generalmente junto con la sustitución de los métodos heredados Object.Equalsy Object.GetHashCode) en todos sus tipos personalizados. En el caso de tipos compuestos, invoque el Equalsmétodo de los tipos contenidos dentro de los tipos contenedores. Para colecciones contenidas, use el SequenceEqualmétodo de extensión, que llama internamente IEquatable<T>.Equalso Object.Equalsen cada elemento. Obviamente, este enfoque requerirá que extienda 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 anidamiento.

Para los tipos de valor, normalmente puede simplemente llamar a su Equalsmétodo. Incluso si los campos o las propiedades nunca se asignaron explícitamente, seguirían teniendo un valor predeterminado.

Para los tipos de referencia, primero debe llamar ReferenceEquals, lo que verifica la igualdad de referencia; esto serviría como un impulso de eficiencia cuando se hace referencia al mismo objeto. También manejaría casos en los que ambas referencias sean nulas. Si esa verificación falla, confirme que el campo o la propiedad de su instancia no sea nulo (para evitar NullReferenceException) y llame a su Equalsmétodo. Dado que nuestros miembros están escritos correctamente, el IEquatable<T>.Equalsmétodo se llama directamente, sin pasar por el Object.Equalsmétodo anulado (cuya ejecución sería un poco más lenta debido al tipo de conversión).

Cuando anula 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);
    }
}

Actualización : esta respuesta fue escrita hace varios años. Desde entonces, comencé a alejarme de la implementación IEquality<T>de tipos mutables para tales escenarios. Hay dos nociones de igualdad: identidad y equivalencia . A nivel de representación de la memoria, estos se distinguen popularmente como "igualdad de referencia" e "igualdad de valor" (consulte Comparaciones de igualdad ). Sin embargo, la misma distinción también se puede aplicar a nivel de dominio. Suponga que su Personclase tiene una PersonIdpropiedad, única por persona distinta del mundo real. Si dos objetos con los mismos PersonId, pero diferentes Agevalores consideradas iguales o diferentes? La respuesta anterior supone que se busca la equivalencia. Sin embargo, hay muchos usos delIEquality<T>interfaz, como colecciones, que asumen que tales implementaciones proporcionan identidad . Por ejemplo, si está rellenando un HashSet<T>, normalmente esperaría que una TryGetValue(T,T)llamada devuelva elementos existentes que comparten simplemente la identidad de su argumento, no necesariamente elementos equivalentes cuyos contenidos son completamente iguales. Esta noción es reforzada por las notas sobre GetHashCode:

En general, para tipos de referencia mutables, debe anular GetHashCode()solo si:

  • Puede calcular el código hash a partir de campos que no son mutables; o
  • Puede asegurarse de que el código hash de un objeto mutable no cambie mientras el objeto esté contenido en una colección que se base en su código hash.
Douglas
fuente
Obtengo este objeto a través de los servicios RIA ... ¿Puedo usar IEquatable <Foo> para esos objetos y obtenerlo en el cliente WPF?
Desarrollador
1
¿Quiere decir que las clases se generan automáticamente? No he usado los servicios RIA, pero supongo que cualquier clase generada se declararía como partial, en cuyo caso, sí, puede implementar su Equalsmétodo a través de una declaración de clase parcial agregada manualmente que hace referencia a campos / propiedades de la generación automática uno.
Douglas
¿Qué pasa si "Dirección dirección" es en realidad "Dirección [] direcciones", cómo se implementaría?
guiomie
2
Se le podría llamar el LINQ Enumerable.SequenceEqualmétodo de las matrices: this.Addresses.SequenceEqual(other.Addresses). Esto llamaría internamente a su Address.Equalsmétodo para cada par de direcciones correspondientes, dado que la Addressclase implementa la IEquatable<Address>interfaz.
Douglas
2
Otra categoría de comparación que un desarrollador podría comprobar es "WorksLike". Para mí, esto significa que aunque dos instancias pueden tener valores de propiedad desiguales, el programa producirá el mismo resultado al procesar las dos instancias.
John Kurtz
95

Serialice ambos objetos y compare las cadenas resultantes

JoelFan
fuente
1
No veo por qué habría. La serialización es normalmente un proceso optimizado y debe acceder al valor de cada propiedad en cualquier caso.
JoelFan
5
Hay un costo enorme. Está generando un flujo de datos, agregando cadenas y luego probando la igualdad de cadenas. Órdenes de magnitud, solo en eso. Sin mencionar que la serialización utilizará la reflexión de forma predeterminada.
Jerome Haltom
2
El flujo de datos no es gran cosa, no veo por qué necesitaría agregar cadenas ... probar la igualdad de cadenas es una de las operaciones más optimizadas que existen ... es posible que tenga un punto con la reflexión ... pero en toda la serialización no será "órdenes de magnitud" peor que otros métodos. Debería hacer evaluaciones comparativas si sospecha problemas de rendimiento ... No he experimentado problemas de rendimiento con este método
JoelFan
12
Soy +1así simplemente porque nunca pensé en hacer una comparación de igualdad basada en valores de esta manera. Es bonito y sencillo. Sería bueno ver algunos puntos de referencia con este código.
Thomas
1
Esa no es una buena solución ya que ambas serializaciones pueden salir mal de manera similar. Por ejemplo, es posible que algunas propiedades del objeto de origen no se hayan serializado y, cuando se deserialicen, se establecerán como nulas en el objeto de destino. En tal caso, su prueba que compara cadenas pasará, ¡pero ambos objetos en realidad no son iguales!
stackMeUp
35

Puede utilizar el método de extensión, recursividad 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) Puede 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;
}
Jonathan
fuente
1
¡La primera solución es genial! Me gusta que no tenga que serializar json o implementar agregar ningún código a los objetos en sí. Adecuado cuando solo está comparando para pruebas unitarias. ¿Puedo sugerir agregar una comparación simple en caso de que objValue y anotherValue sean ambos iguales a null? Esto evitará que se lance una NullReferenceException al intentar hacer null.Equals () // ReSharper deshabilitar una vez RedundantJumpStatement if (objValue == anotherValue) continue; // protección de referencia nula else if (! objValue.Equals (anotherValue)) Fail (esperado, actual);
Mark Conway
3
¿Hay alguna razón para usar en DeepComparelugar de simplemente llamar de forma CompareExrecursiva?
apostolov
3
Esto puede comparar toda la estructura innecesariamente. Reemplazarlo resultcon return falselo haría más eficiente.
Tim Sylvester
24

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 de forma recursiva para comparar sus propiedades "internas" .

No estoy pensando en el rendimiento, sino en la sencillez. Depende, sin embargo, del diseño exacto de sus objetos. Podría complicarse dependiendo de la forma de sus objetos (por ejemplo, si hay dependencias cíclicas entre propiedades). Sin embargo, existen varias soluciones que puede utilizar, como esta:

Otra opción es serializar el objeto como texto, por ejemplo, usando JSON.NET y comparando el resultado de la serialización. (JSON.NET puede manejar dependencias cíclicas entre propiedades).

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

JotaBe
fuente
1
No creo que una IEquatable<T>implementación califique como un caso de optimización prematura. La reflexión será drásticamente más lenta. La implementación predeterminada de Equalspara tipos de valores personalizados utiliza la reflexión; Microsoft mismo recomienda anularlo por rendimiento: "Anule el Equalsmétodo para un tipo en particular para mejorar el rendimiento del método y representar más de cerca el concepto de igualdad para el tipo".
Douglas
1
Depende de cuántas veces vaya a ejecutar el método de iguales: 1, 10, 100, 100, ¿un millón? Eso marcará una gran diferencia. Si puede utilizar una solución genérica sin implementar nada, dedicará un tiempo precioso. Si es demasiado lento, entonces es hora de implementar IEquatable (y quizás incluso intentar hacer un GetHashCode inteligente o en caché) En cuanto a la velocidad de Reflection, debo estar de acuerdo en que es más lento ... o mucho más lento, dependiendo de cómo lo hagas ( es decir, reutilizar los tipos PropertyInfos y así sucesivamente, o no).
JotaBe
@ Worthy7 Lo hace. Por favor, consulte el contenido del proyecto. Las pruebas son una buena forma de documentar con el ejemplo. Pero, mejor que eso, si lo busca, encontrará un archivo de ayuda .chm. Entonces, este proyecto tiene una documentación mucho mejor que la mayoría de los proyectos.
JotaBe
Lo siento, tienes razón, me perdí totalmente la pestaña "wiki". Estoy acostumbrado a que todo el mundo escriba cosas en el archivo Léame.
Worthy7
9

Serialice ambos objetos y compare las cadenas resultantes por @JoelFan

Entonces, para hacer esto, cree una clase estática como esta y use Extensions para extender TODOS los objetos (para que pueda 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 simplemente puede usar .Equals para compararlos. Utilizo esto para verificar si los objetos también están en colecciones. Funciona muy bien.

ozzy432836
fuente
¿Qué pasa si el contenido de los objetos son matrices de números de coma flotante? Es muy ineficiente convertirlos en cadenas, y la conversión está sujeta a las transformaciones definidas en CultrureInfo. Esto solo funcionaría si los datos internos son principalmente cadenas y números enteros. De lo contrario, sería un desastre.
John Alexiou
3
¿Qué pasa si un nuevo director le dice que elimine C # y lo reemplace con Python? Como desarrolladores, tenemos que aprender que las preguntas y si tienen que detenerse en algún lado. Resuelva el problema, pase al siguiente. Si alguna vez tienes tiempo, vuelve a él ...
ozzy432836
2
Python se parece más a MATLAB en sintaxis y uso. Debe haber una muy buena razón para pasar de un lenguaje seguro de tipo estático a un script de mezcolanza como Python.
John Alexiou
5

Asumiré que no te refieres literalmente a los mismos objetos.

Object1 == Object2

Quizás esté 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 puede simplemente comparar la igualdad de dos objetos de manera agnóstica. Tendrá que comparar cada valor, uno a la vez, de forma personalizada.

Considere agregar la IEquatable<T>interfaz a su clase y defina un Equalsmétodo personalizado para su tipo. Luego, en ese método, pruebe manualmente cada valor. Agregue IEquatable<T>nuevamente los tipos adjuntos si puede y repita el proceso.

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

Serialice ambos objetos, luego calcule el código hash y luego compare.

Sk Shahnawaz-ul Haque
fuente
3

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 me está funcionando bien.

Akshay
fuente
1
El primero ifsignifica aquello Compare(null, null) == falseque no es lo que esperaría.
Tim Sylvester
3

Basado en algunas respuestas ya dadas aquí, decidí respaldar principalmente la respuesta de JoelFan . Me encantan los métodos de extensión y estos han funcionado muy bien para mí cuando ninguna de las otras soluciones los usaba para comparar mis clases complejas.

Métodos de extensión

using System.IO;
using System.Xml.Serialization;

static class ObjectHelpers
{
    public static string SerializeObject<T>(this T toSerialize)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());

        using (StringWriter textWriter = new StringWriter())
        {
            xmlSerializer.Serialize(textWriter, toSerialize);
            return textWriter.ToString();
        }
    }

    public static bool EqualTo(this object obj, object toCompare)
    {
        if (obj.SerializeObject() == toCompare.SerializeObject())
            return true;
        else
            return false;
    }

    public static bool IsBlank<T>(this T obj) where T: new()
    {
        T blank = new T();
        T newObj = ((T)obj);

        if (newObj.SerializeObject() == blank.SerializeObject())
            return true;
        else
            return false;
    }

}

Ejemplos de uso

if (record.IsBlank())
    throw new Exception("Record found is blank.");

if (record.EqualTo(new record()))
    throw new Exception("Record found is blank.");
Arvo Bowen
fuente
2

Yo diría que:

Object1.Equals(Object2)

sería lo que estás buscando. Eso es si está buscando para ver si los objetos son iguales, que es lo que parece estar preguntando.

Si desea verificar si todos los objetos secundarios son iguales, ejecútelos a través de un ciclo con el Equals()método.

PaulG
fuente
2
Si y solo si proporcionan una sobrecarga de igualdad no referencial de Equals.
user7116
Cada clase debe implementar su propia forma de comparación. Si las clases del autor no tienen anulaciones para el método Equals (), usarán el método básico de la clase System.Object (), lo que conducirá a errores en la lógica.
Dima
2
public class GetObjectsComparison
{
    public object FirstObject, SecondObject;
    public BindingFlags BindingFlagsConditions= BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
}
public struct SetObjectsComparison
{
    public FieldInfo SecondObjectFieldInfo;
    public dynamic FirstObjectFieldInfoValue, SecondObjectFieldInfoValue;
    public bool ErrorFound;
    public GetObjectsComparison GetObjectsComparison;
}
private static bool ObjectsComparison(GetObjectsComparison GetObjectsComparison)
{
    GetObjectsComparison FunctionGet = GetObjectsComparison;
    SetObjectsComparison FunctionSet = new SetObjectsComparison();
    if (FunctionSet.ErrorFound==false)
        foreach (FieldInfo FirstObjectFieldInfo in FunctionGet.FirstObject.GetType().GetFields(FunctionGet.BindingFlagsConditions))
        {
            FunctionSet.SecondObjectFieldInfo =
            FunctionGet.SecondObject.GetType().GetField(FirstObjectFieldInfo.Name, FunctionGet.BindingFlagsConditions);

            FunctionSet.FirstObjectFieldInfoValue = FirstObjectFieldInfo.GetValue(FunctionGet.FirstObject);
            FunctionSet.SecondObjectFieldInfoValue = FunctionSet.SecondObjectFieldInfo.GetValue(FunctionGet.SecondObject);
            if (FirstObjectFieldInfo.FieldType.IsNested)
            {
                FunctionSet.GetObjectsComparison =
                new GetObjectsComparison()
                {
                    FirstObject = FunctionSet.FirstObjectFieldInfoValue
                    ,
                    SecondObject = FunctionSet.SecondObjectFieldInfoValue
                };

                if (!ObjectsComparison(FunctionSet.GetObjectsComparison))
                {
                    FunctionSet.ErrorFound = true;
                    break;
                }
            }
            else if (FunctionSet.FirstObjectFieldInfoValue != FunctionSet.SecondObjectFieldInfoValue)
            {
                FunctionSet.ErrorFound = true;
                break;
            }
        }
    return !FunctionSet.ErrorFound;
}
matan justme
fuente
Usando los principios de la recursividad.
matan justme
1

Una forma de hacer esto sería anular Equals()en cada tipo involucrado. Por ejemplo, su objeto de nivel superior anularía Equals()para llamar al Equals()método de los 5 objetos secundarios. Todos esos objetos también deben anularse Equals(), asumiendo que son objetos personalizados, y así sucesivamente hasta que se pueda comparar toda la jerarquía con solo realizar una verificación de igualdad en los objetos de nivel superior.

goric
fuente
1

Utilice la IEquatable<T>interfaz que tiene un método Equals.

Jefe principal
fuente
1

Gracias al ejemplo de Jonathan. Lo expandí para todos los casos (matrices, listas, diccionarios, tipos primitivos).

Esta es una comparación sin serialización y no requiere la implementación de ninguna interfaz para los objetos comparados.

        /// <summary>Returns description of difference or empty value if equal</summary>
        public static string Compare(object obj1, object obj2, string path = "")
        {
            string path1 = string.IsNullOrEmpty(path) ? "" : path + ": ";
            if (obj1 == null && obj2 != null)
                return path1 + "null != not null";
            else if (obj2 == null && obj1 != null)
                return path1 + "not null != null";
            else if (obj1 == null && obj2 == null)
                return null;

            if (!obj1.GetType().Equals(obj2.GetType()))
                return "different types: " + obj1.GetType() + " and " + obj2.GetType();

            Type type = obj1.GetType();
            if (path == "")
                path = type.Name;

            if (type.IsPrimitive || typeof(string).Equals(type))
            {
                if (!obj1.Equals(obj2))
                    return path1 + "'" + obj1 + "' != '" + obj2 + "'";
                return null;
            }
            if (type.IsArray)
            {
                Array first = obj1 as Array;
                Array second = obj2 as Array;
                if (first.Length != second.Length)
                    return path1 + "array size differs (" + first.Length + " vs " + second.Length + ")";

                var en = first.GetEnumerator();
                int i = 0;
                while (en.MoveNext())
                {
                    string res = Compare(en.Current, second.GetValue(i), path);
                    if (res != null)
                        return res + " (Index " + i + ")";
                    i++;
                }
            }
            else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type))
            {
                System.Collections.IEnumerable first = obj1 as System.Collections.IEnumerable;
                System.Collections.IEnumerable second = obj2 as System.Collections.IEnumerable;

                var en = first.GetEnumerator();
                var en2 = second.GetEnumerator();
                int i = 0;
                while (en.MoveNext())
                {
                    if (!en2.MoveNext())
                        return path + ": enumerable size differs";

                    string res = Compare(en.Current, en2.Current, path);
                    if (res != null)
                        return res + " (Index " + i + ")";
                    i++;
                }
            }
            else
            {
                foreach (PropertyInfo pi in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
                {
                    try
                    {
                        var val = pi.GetValue(obj1);
                        var tval = pi.GetValue(obj2);
                        if (path.EndsWith("." + pi.Name))
                            return null;
                        var pathNew = (path.Length == 0 ? "" : path + ".") + pi.Name;
                        string res = Compare(val, tval, pathNew);
                        if (res != null)
                            return res;
                    }
                    catch (TargetParameterCountException)
                    {
                        //index property
                    }
                }
                foreach (FieldInfo fi in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
                {
                    var val = fi.GetValue(obj1);
                    var tval = fi.GetValue(obj2);
                    if (path.EndsWith("." + fi.Name))
                        return null;
                    var pathNew = (path.Length == 0 ? "" : path + ".") + fi.Name;
                    string res = Compare(val, tval, pathNew);
                    if (res != null)
                        return res;
                }
            }
            return null;
        }

Para copiar fácilmente el repositorio creado con código

Alexey Obukhov
fuente
1

Ahora puede usar json.net. Simplemente vaya a Nuget e instálelo.

Y puedes hacer algo como esto:

    public bool Equals(SamplesItem sampleToCompare)
    {
        string myself = JsonConvert.SerializeObject(this);
        string other = JsonConvert.SerializeObject(sampleToCompare);

        return myself == other;
    }

Quizás podría crear un método de extensión para el objeto si quisiera ser más elegante. Tenga en cuenta que esto solo compara las propiedades públicas. Y si desea ignorar una propiedad pública cuando realiza la comparación, puede usar el atributo [JsonIgnore].

sillar64
fuente
Si tiene listas en sus objetos y esos tienen listas, entonces intentar atravesar ambos objetos será una pesadilla. Si serializa ambos y luego compara, no tendrá que lidiar con ese escenario.
ashlar64
Si su objeto complejo tiene un Diccionario, no creo que el serializador .net pueda serializarlo. El serializador Json puede.
ashlar64