J'ai deux objets complexes comme Object1et Object2. Ils ont environ 5 niveaux d'objets enfants.
J'ai besoin de la méthode la plus rapide pour dire s'ils sont identiques ou non.
Comment cela pourrait-il être fait en C # 4.0?
J'ai deux objets complexes comme Object1et Object2. Ils ont environ 5 niveaux d'objets enfants.
J'ai besoin de la méthode la plus rapide pour dire s'ils sont identiques ou non.
Comment cela pourrait-il être fait en C # 4.0?
Réponses:
Implémentez IEquatable<T>(généralement en même temps que le remplacement des méthodes Object.Equalset héritées Object.GetHashCode) sur tous vos types personnalisés. Dans le cas des types composites, invoquez la Equalsméthode des types contenus dans les types conteneurs . Pour les collections contenues, utilisez la SequenceEqualméthode d'extension, qui appelle en interne IEquatable<T>.Equalsou Object.Equalssur chaque élément. Cette approche vous obligera évidemment à étendre les définitions de vos types, mais ses résultats sont plus rapides que toutes les solutions génériques impliquant la sérialisation.
Edit : Voici un exemple artificiel avec trois niveaux d'imbrication.
Pour les types valeur, vous pouvez généralement simplement appeler leur Equalsméthode. Même si les champs ou les propriétés n'étaient jamais explicitement affectés, ils auraient toujours une valeur par défaut.
Pour les types de référence, vous devez d'abord appeler ReferenceEquals, qui vérifie l'égalité des références - cela servirait d'amélioration de l'efficacité lorsque vous référencez le même objet. Il traiterait également les cas où les deux références sont nulles. Si cette vérification échoue, confirmez que le champ ou la propriété de votre instance n'est pas nul (à éviter NullReferenceException) et appelez sa Equalsméthode. Puisque nos membres sont correctement typés, la IEquatable<T>.Equalsméthode est appelée directement, en contournant la Object.Equalsméthode surchargée (dont l'exécution serait légèrement plus lente en raison du type cast).
Lorsque vous remplacez Object.Equals, vous êtes également censé remplacer Object.GetHashCode; Je ne l'ai pas fait ci-dessous par souci de concision.
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);
}
}
Mise à jour : Cette réponse a été écrite il y a plusieurs années. Depuis lors, j'ai commencé à m'éloigner de l'implémentation IEquality<T>de types mutables pour de tels scénarios. Il existe deux notions d'égalité: l' identité et l' équivalence . Au niveau de la représentation de la mémoire, ils sont généralement distingués comme «égalité de référence» et «égalité de valeur» (voir Comparaisons d'égalité ). Cependant, la même distinction peut également s'appliquer au niveau du domaine. Supposons que votre Personclasse possède une PersonIdpropriété, unique par personne distincte du monde réel. Est-ce que deux objets avec les mêmes valeurs PersonIdmais différentes Agedoivent être considérés comme égaux ou différents? La réponse ci-dessus suppose que l'on est après l'équivalence. Cependant, il existe de nombreux usages duIEquality<T>interface, comme les collections, qui supposent que de telles implémentations fournissent une identité . Par exemple, si vous remplissez a HashSet<T>, vous vous attendez généralement à ce qu'un TryGetValue(T,T)appel renvoie des éléments existants qui partagent simplement l'identité de votre argument, pas nécessairement des éléments équivalents dont le contenu est complètement le même. Cette notion est renforcée par les notes sur GetHashCode:
En général, pour les types de référence mutables, vous ne devez remplacer
GetHashCode()que si:
- Vous pouvez calculer le code de hachage à partir de champs qui ne sont pas modifiables; ou
- Vous pouvez vous assurer que le code de hachage d'un objet mutable ne change pas tant que l'objet est contenu dans une collection qui repose sur son code de hachage.
partial- auquel cas, oui, vous pouvez implémenter leur Equalsméthode via une déclaration de classe partielle ajoutée manuellement qui fait référence aux champs / propriétés de la génération automatique une.
Enumerable.SequenceEqualméthode sur les tableaux: this.Addresses.SequenceEqual(other.Addresses). Cela appellerait en interne votre Address.Equalsméthode pour chaque paire d'adresses correspondantes, étant donné que la Addressclasse implémente l' IEquatable<Address>interface.
Sérialiser les deux objets et comparer les chaînes résultantes
+1simplement parce que je n'ai jamais pensé à faire une comparaison d'égalité basée sur des valeurs de cette façon. C'est sympa et simple. Ce serait bien de voir quelques points de repère avec ce code.
Vous pouvez utiliser la méthode d'extension, la récursivité pour résoudre ce problème:
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 comparer en utilisant Json (si l'objet est très complexe) Vous pouvez utiliser 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;
}
DeepCompareau lieu d'appeler simplement CompareExrécursivement?
resultpar return falsele rendrait plus efficace.
Si vous ne souhaitez pas implémenter IEquatable, vous pouvez toujours utiliser Reflection pour comparer toutes les propriétés: - si elles sont de type valeur, comparez-les simplement - si elles sont de type référence, appelez la fonction de manière récursive pour comparer ses propriétés "internes" .
Je ne pense pas à la performance, mais à la simplicité. Cela dépend cependant de la conception exacte de vos objets. Cela peut devenir compliqué en fonction de la forme de vos objets (par exemple s'il existe des dépendances cycliques entre les propriétés). Il existe cependant plusieurs solutions que vous pouvez utiliser, comme celle-ci:
Une autre option consiste à sérialiser l'objet sous forme de texte, par exemple à l'aide de JSON.NET, et à comparer le résultat de la sérialisation. (JSON.NET peut gérer les dépendances cycliques entre les propriétés).
Je ne sais pas si par plus rapide vous entendez le moyen le plus rapide de l'implémenter ou un code qui s'exécute rapidement. Vous ne devez pas optimiser avant de savoir si vous en avez besoin. L'optimisation prématurée est la racine de tout Mal
IEquatable<T>implémentation puisse être qualifiée de cas d'optimisation prématurée. La réflexion sera considérablement plus lente. L'implémentation par défaut de Equalspour les types de valeur personnalisés utilise la réflexion; Microsoft lui-même recommande de le remplacer pour les performances: «Remplacez la Equalsméthode pour un type particulier pour améliorer les performances de la méthode et représenter plus fidèlement le concept d'égalité pour le type.»
Sérialisez les deux objets et comparez les chaînes résultantes par @JoelFan
Donc, pour ce faire, créez une classe statique comme celle-ci et utilisez les extensions pour étendre TOUS les objets (afin que vous puissiez passer n'importe quel type d'objet, de collection, etc. dans la méthode)
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());
}
}
}
Une fois que vous avez référencé cette classe statique dans un autre fichier, vous pouvez le faire:
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();
Vous pouvez maintenant simplement utiliser .Equals pour les comparer. J'utilise ceci pour vérifier si les objets sont également dans des collections. Cela fonctionne vraiment bien.
CultrureInfo. Cela ne fonctionnerait que si les données internes sont principalement des chaînes et des entiers. Sinon, ce serait un désastre.
Je suppose que vous ne faites pas référence aux mêmes objets
Object1 == Object2
Vous pensez peut-être faire une comparaison de mémoire entre les deux
memcmp(Object1, Object2, sizeof(Object.GetType())
Mais ce n'est même pas du vrai code en c # :). Étant donné que toutes vos données sont probablement créées sur le tas, la mémoire n'est pas contiguë et vous ne pouvez pas simplement comparer l'égalité de deux objets de manière agnostique. Vous allez devoir comparer chaque valeur, une à la fois, de manière personnalisée.
Envisagez d'ajouter l' IEquatable<T>interface à votre classe et définissez une Equalsméthode personnalisée pour votre type. Ensuite, dans cette méthode, testez manuellement chaque valeur. Ajoutez à IEquatable<T>nouveau les types inclus si vous le pouvez et répétez le processus.
class Foo : IEquatable<Foo>
{
public bool Equals(Foo other)
{
/* check all the values */
return false;
}
}
Sérialisez les deux objets, puis calculez le code de hachage, puis comparez.
J'ai trouvé cette fonction ci-dessous pour comparer des objets.
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;
}
Je l'utilise et cela fonctionne très bien pour moi.
ifsignifie ce Compare(null, null) == falsequi n'est pas ce à quoi je m'attendais.
Sur la base de quelques réponses déjà données ici, j'ai décidé de soutenir principalement la réponse de JoelFan . J'adore les méthodes d'extension et celles-ci fonctionnent très bien pour moi alors qu'aucune des autres solutions ne les utiliserait pour comparer mes classes complexes.
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.");
Je dirais que:
Object1.Equals(Object2)
serait ce que vous recherchez. C'est si vous cherchez à voir si les objets sont les mêmes, ce que vous semblez demander.
Si vous souhaitez vérifier si tous les objets enfants sont identiques, exécutez-les via une boucle avec la Equals()méthode.
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;
}
Une façon de le faire serait de remplacer Equals()chaque type impliqué. Par exemple, votre objet de niveau supérieur remplacerait Equals()pour appeler la Equals()méthode des 5 objets enfants. Ces objets doivent également tous être remplacés Equals(), en supposant qu'il s'agit d'objets personnalisés, et ainsi de suite jusqu'à ce que toute la hiérarchie puisse être comparée en effectuant simplement une vérification d'égalité sur les objets de niveau supérieur.
Utilisez l' IEquatable<T>interface qui a une méthode Equals.
Merci à l'exemple de Jonathan. Je l'ai développé pour tous les cas (tableaux, listes, dictionnaires, types primitifs).
Il s'agit d'une comparaison sans sérialisation et ne nécessite l'implémentation d'aucune interface pour les objets comparés.
/// <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;
}
Pour une copie facile du référentiel de code créé
Vous pouvez maintenant utiliser json.net. Allez simplement sur Nuget et installez-le.
Et vous pouvez faire quelque chose comme ceci:
public bool Equals(SamplesItem sampleToCompare)
{
string myself = JsonConvert.SerializeObject(this);
string other = JsonConvert.SerializeObject(sampleToCompare);
return myself == other;
}
Vous pourriez peut-être créer une méthode d'extension pour object si vous voulez devenir plus sophistiqué. Veuillez noter que cela ne compare que les propriétés publiques. Et si vous souhaitez ignorer une propriété publique lorsque vous effectuez la comparaison, vous pouvez utiliser l'attribut [JsonIgnore].