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?
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?
Respuestas:
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.
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.
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.
Serialice ambos objetos y compare las cadenas resultantes
+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.
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;
}
DeepComparelugar de simplemente llamar de forma CompareExrecursiva?
resultcon return falselo haría más eficiente.
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
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".
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.
CultrureInfo. Esto solo funcionaría si los datos internos son principalmente cadenas y números enteros. De lo contrario, sería un desastre.
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;
}
}
Serialice ambos objetos, luego calcule el código hash y luego compare.
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.
ifsignifica aquello Compare(null, null) == falseque no es lo que esperaría.
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.
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;
}
}
if (record.IsBlank())
throw new Exception("Record found is blank.");
if (record.EqualTo(new record()))
throw new Exception("Record found is blank.");
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.
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;
}
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.
Utilice la IEquatable<T>interfaz que tiene un método Equals.
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
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].