Tenho dois objetos complexos como Object1e Object2. Eles têm cerca de 5 níveis de objetos filhos.
Preciso do método mais rápido para dizer se eles são iguais ou não.
Como isso pode ser feito no C # 4.0?
Tenho dois objetos complexos como Object1e Object2. Eles têm cerca de 5 níveis de objetos filhos.
Preciso do método mais rápido para dizer se eles são iguais ou não.
Como isso pode ser feito no C # 4.0?
Respostas:
Implemente IEquatable<T>(normalmente em conjunto com a substituição dos métodos Object.Equalse herdados Object.GetHashCode) em todos os seus tipos personalizados. No caso de tipos compostos, invoque o Equalsmétodo dos tipos contidos nos tipos contidos . Para coleções contidas, use o SequenceEqualmétodo de extensão, que chama internamente IEquatable<T>.Equalsou Object.Equalsem cada elemento. Essa abordagem obviamente exigirá que você estenda as definições de seus tipos, mas seus resultados são mais rápidos do que quaisquer soluções genéricas envolvendo serialização.
Edit : Aqui está um exemplo inventado com três níveis de aninhamento.
Para tipos de valor, normalmente você pode apenas chamar seu Equalsmétodo. Mesmo que os campos ou propriedades nunca tenham sido atribuídos explicitamente, eles ainda terão um valor padrão.
Para tipos de referência, você deve primeiro chamar ReferenceEquals, que verifica a igualdade de referência - isso serviria como um aumento de eficiência quando você estiver fazendo referência ao mesmo objeto. Ele também lidaria com casos em que ambas as referências são nulas. Se essa verificação falhar, confirme se o campo ou propriedade da instância não é nulo (para evitar NullReferenceException) e chame seu Equalsmétodo. Uma vez que nossos membros são digitados corretamente, o IEquatable<T>.Equalsmétodo é chamado diretamente, ignorando o Object.Equalsmétodo sobrescrito (cuja execução seria um pouco mais lenta devido ao tipo de conversão).
Quando você substitui Object.Equals, também é esperado que você substitua Object.GetHashCode; Eu não fiz isso abaixo por uma questão de concisão.
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);
}
}
Atualização : Esta resposta foi escrita há vários anos. Desde então, comecei a me afastar da implementação IEquality<T>de tipos mutáveis para esses cenários. Existem duas noções de igualdade: identidade e equivalência . Em um nível de representação de memória, eles são popularmente distinguidos como “igualdade de referência” e “igualdade de valor” (consulte Comparações de igualdade ). No entanto, a mesma distinção também pode ser aplicada em um nível de domínio. Suponha que sua Personclasse tenha uma PersonIdpropriedade, única por pessoa distinta do mundo real. Devem dois objetos com os mesmos valores, PersonIdmas diferentes Age, ser considerados iguais ou diferentes? A resposta acima assume que se busca a equivalência. No entanto, existem muitos usos doIEquality<T>interface, como coleções, que assumem que tais implementações fornecem identidade . Por exemplo, se você estiver preenchendo um HashSet<T>, normalmente esperaria que uma TryGetValue(T,T)chamada retornasse elementos existentes que compartilham apenas a identidade de seu argumento, não necessariamente elementos equivalentes cujo conteúdo é completamente o mesmo. Esta noção é reforçada pelas notas sobre GetHashCode:
Em geral, para tipos de referência mutáveis, você deve substituir
GetHashCode()apenas se:
- Você pode calcular o código hash de campos que não são mutáveis; ou
- Você pode garantir que o código hash de um objeto mutável não seja alterado enquanto o objeto estiver contido em uma coleção que depende de seu código hash.
partial- nesse caso, sim, você pode implementar seu Equalsmétodo por meio de uma declaração de classe parcial adicionada manualmente que faz referência a campos / propriedades do gerado automaticamente 1.
Enumerable.SequenceEqualmétodo nas matrizes: this.Addresses.SequenceEqual(other.Addresses). Isso chamaria internamente seu Address.Equalsmétodo para cada par de endereços correspondentes, visto que a Addressclasse implementa a IEquatable<Address>interface.
Serialize os dois objetos e compare as strings resultantes
+1isso simplesmente porque nunca pensei em fazer uma comparação de igualdade baseada em valores dessa maneira. É bom e simples. Seria legal ver alguns benchmarks com este código.
Você pode usar o método de extensão, recursão 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;
}
ou compare usando Json (se o objeto for muito complexo) Você pode 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;
}
DeepComparevez de simplesmente chamar CompareExrecursivamente?
resultpor return falseo tornaria mais eficiente.
Se você não quiser implementar IEquatable, você pode sempre usar o Reflection para comparar todas as propriedades: - se eles forem do tipo de valor, apenas compare-os -se forem do tipo de referência, chame a função recursivamente para comparar suas propriedades "internas" .
Não estou pensando em performace, mas sim em simplicidade. Depende, entretanto, do design exato de seus objetos. Pode ficar complicado dependendo da forma dos seus objetos (por exemplo, se houver dependências cíclicas entre propriedades). No entanto, existem várias soluções que você pode usar, como esta:
Outra opção é serializar o objeto como texto, por exemplo, usando JSON.NET e comparando o resultado da serialização. (JSON.NET pode lidar com dependências cíclicas entre propriedades).
Não sei se por mais rápido você quer dizer a maneira mais rápida de implementá-lo ou um código que roda rápido. Você não deve otimizar antes de saber se é necessário. Otimização prematura é a raiz de todo o mal
IEquatable<T>implementação se qualifica como um caso de otimização prematura. A reflexão será drasticamente mais lenta. A implementação padrão de Equalspara tipos de valor customizado usa reflexão; A própria Microsoft recomenda substituí-lo para desempenho: “Substitua o Equalsmétodo de um tipo específico para melhorar o desempenho do método e representar mais de perto o conceito de igualdade para o tipo.”
Serialize ambos os objetos e compare as strings resultantes por @JoelFan
Então, para fazer isso, crie uma classe estática como essa e use Extensões para estender TODOS os objetos (para que você possa passar qualquer tipo de objeto, coleção, etc. para o 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());
}
}
}
Depois de fazer referência a essa classe estática em qualquer outro arquivo, você pode fazer isso:
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();
Agora você pode simplesmente usar .Equals para compará-los. Eu uso isso para verificar se os objetos estão em coleções também. Funciona muito bem.
CultrureInfo. Isso só funcionaria se os dados internos fossem principalmente strings e inteiros. Caso contrário, seria um desastre.
Vou assumir que você não está se referindo literalmente aos mesmos objetos
Object1 == Object2
Você pode estar pensando em fazer uma comparação de memória entre os dois
memcmp(Object1, Object2, sizeof(Object.GetType())
Mas isso nem mesmo é código real em c # :). Como todos os seus dados provavelmente são criados no heap, a memória não é contígua e você não pode simplesmente comparar a igualdade de dois objetos de maneira agnóstica. Você terá que comparar cada valor, um de cada vez, de uma forma personalizada.
Considere adicionar a IEquatable<T>interface à sua classe e definir um Equalsmétodo personalizado para o seu tipo. Então, nesse método, teste manualmente cada valor. Adicione IEquatable<T>novamente os tipos fechados, se possível, e repita o processo.
class Foo : IEquatable<Foo>
{
public bool Equals(Foo other)
{
/* check all the values */
return false;
}
}
Serialize os dois objetos, calcule o código hash e compare.
Encontrei esta função abaixo 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;
}
Estou usando e está funcionando bem para mim.
ifsignifica aquilo Compare(null, null) == falseque não é o que eu esperava.
Com base em algumas respostas já fornecidas aqui, decidi apoiar principalmente a resposta de JoelFan . Eu amo métodos de extensão e eles têm funcionado muito bem para mim quando nenhuma das outras soluções os usaria para comparar minhas classes complexas.
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.");
Eu diria que:
Object1.Equals(Object2)
seria o que você está procurando. Isso se você estiver procurando ver se os objetos são os mesmos, que é o que parece estar perguntando.
Se você deseja verificar se todos os objetos filhos são iguais, execute-os em um loop com o 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;
}
Uma maneira de fazer isso seria substituir Equals()em cada tipo envolvido. Por exemplo, seu objeto de nível superior seria substituído Equals()para chamar o Equals()método de todos os 5 objetos filho. Esses objetos também devem ser substituídos Equals(), assumindo que sejam objetos personalizados, e assim por diante, até que toda a hierarquia possa ser comparada apenas executando uma verificação de igualdade nos objetos de nível superior.
Use a IEquatable<T>Interface que possui um método Equals.
Graças ao exemplo de Jonathan. Eu o expandi para todos os casos (arrays, listas, dicionários, tipos primitivos).
Esta é uma comparação sem serialização e não requer a implementação de nenhuma interface para 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 cópia fácil do repositório de código criado
Agora você pode usar json.net. Basta acessar o Nuget e instalá-lo.
E você pode fazer algo assim:
public bool Equals(SamplesItem sampleToCompare)
{
string myself = JsonConvert.SerializeObject(this);
string other = JsonConvert.SerializeObject(sampleToCompare);
return myself == other;
}
Você poderia talvez fazer um método de extensão para o objeto se quiser ficar mais sofisticado. Observe que isso só compara as propriedades públicas. E se você quiser ignorar uma propriedade pública ao fazer a comparação, poderá usar o atributo [JsonIgnore].