Ho due oggetti complessi come Object1e Object2. Hanno circa 5 livelli di oggetti figlio.
Ho bisogno del metodo più veloce per dire se sono uguali o no.
Come è possibile farlo in C # 4.0?
Ho due oggetti complessi come Object1e Object2. Hanno circa 5 livelli di oggetti figlio.
Ho bisogno del metodo più veloce per dire se sono uguali o no.
Come è possibile farlo in C # 4.0?
Risposte:
Implementa IEquatable<T>(in genere in combinazione con l'override dei metodi Object.Equalse ereditati Object.GetHashCode) su tutti i tipi personalizzati. Nel caso di tipi compositi, richiamare il Equalsmetodo dei tipi contenuti all'interno dei tipi contenenti. Per le raccolte contenute, utilizzare il SequenceEqualmetodo di estensione, che chiama internamente IEquatable<T>.Equalso Object.Equalssu ogni elemento. Questo approccio richiederà ovviamente di estendere le definizioni dei tipi, ma i suoi risultati sono più veloci di qualsiasi soluzione generica che coinvolge la serializzazione.
Modifica : ecco un esempio artificioso con tre livelli di nidificazione.
Per i tipi di valore, in genere puoi semplicemente chiamare il loro Equalsmetodo. Anche se i campi o le proprietà non sono mai stati assegnati in modo esplicito, avrebbero comunque un valore predefinito.
Per i tipi di riferimento, dovresti prima chiamare ReferenceEquals, che verifica l'uguaglianza dei riferimenti: questo servirebbe come un aumento dell'efficienza quando ti capita di fare riferimento allo stesso oggetto. Gestirà anche i casi in cui entrambi i riferimenti sono nulli. Se il controllo fallisce, conferma che il campo o la proprietà dell'istanza non è nullo (per evitare NullReferenceException) e chiama il suo Equalsmetodo. Poiché i nostri membri sono digitati correttamente, il IEquatable<T>.Equalsmetodo viene chiamato direttamente, ignorando il Object.Equalsmetodo sovrascritto (la cui esecuzione sarebbe leggermente più lenta a causa del cast del tipo).
Quando esegui l'override Object.Equals, devi anche eseguire l'override Object.GetHashCode; Non l'ho fatto di seguito per motivi di concisione.
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);
}
}
Aggiornamento : questa risposta è stata scritta diversi anni fa. Da allora, ho iniziato ad allontanarmi dall'implementazione IEquality<T>di tipi mutabili per tali scenari. Esistono due nozioni di uguaglianza: identità ed equivalenza . A livello di rappresentazione della memoria, questi sono comunemente distinti come "uguaglianza di riferimento" e "uguaglianza di valore" (vedere Confronti di uguaglianza ). Tuttavia, la stessa distinzione può essere applicata anche a livello di dominio. Supponi che la tua Personclasse abbia una PersonIdproprietà, unica per persona distinta del mondo reale. Due oggetti con valori uguali PersonIdma diversi Agedevono essere considerati uguali o diversi? La risposta sopra presuppone che uno sia dopo l'equivalenza. Tuttavia, ci sono molti utilizzi diIEquality<T>interfaccia, come le raccolte, che presuppongono che tali implementazioni forniscano l' identità . Ad esempio, se stai popolando un HashSet<T>, in genere ti aspetteresti che una TryGetValue(T,T)chiamata restituisca elementi esistenti che condividono semplicemente l'identità del tuo argomento, non necessariamente elementi equivalenti il cui contenuto è completamente lo stesso. Questa nozione è rafforzata dalle note su GetHashCode:
In generale, per i tipi di riferimento modificabili, è necessario eseguire l'override
GetHashCode()solo se:
- È possibile calcolare il codice hash da campi che non sono modificabili; o
- Puoi assicurarti che il codice hash di un oggetto modificabile non cambi mentre l'oggetto è contenuto in una raccolta che si basa sul suo codice hash.
partial- nel qual caso, sì, puoi implementare il loro Equalsmetodo attraverso una dichiarazione di classe parziale aggiunta manualmente che fa riferimento a campi / proprietà dall'auto-generata uno.
Enumerable.SequenceEqualmetodo su array: this.Addresses.SequenceEqual(other.Addresses). Questo chiamerebbe internamente il tuo Address.Equalsmetodo per ogni coppia di indirizzi corrispondenti, dato che la Addressclasse implementa l' IEquatable<Address>interfaccia.
Serializza entrambi gli oggetti e confronta le stringhe risultanti
+1semplicemente perché non ho mai pensato di fare un confronto di uguaglianza basato sui valori in questo modo. È carino e semplice. Sarebbe bello vedere alcuni benchmark con questo codice.
Puoi utilizzare il metodo di estensione, la ricorsione per risolvere questo 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 confronta usando Json (se l'oggetto è molto complesso) Puoi usare 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;
}
DeepCompareinvece di chiamare semplicemente CompareExricorsivamente?
resultcon return falselo renderebbe più efficiente.
Se non vuoi implementare IEquatable, puoi sempre usare Reflection per confrontare tutte le proprietà: - se sono di tipo valore, confrontali semplicemente -se sono di tipo riferimento, chiama la funzione ricorsivamente per confrontare le sue proprietà "interne" .
Non penso alla performace, ma alla semplicità. Dipende però dal design esatto dei tuoi oggetti. Potrebbe complicarsi a seconda della forma degli oggetti (ad esempio se ci sono dipendenze cicliche tra le proprietà). Esistono, tuttavia, diverse soluzioni che puoi utilizzare, come questa:
Un'altra opzione è serializzare l'oggetto come testo, ad esempio utilizzando JSON.NET e confrontando il risultato della serializzazione. (JSON.NET può gestire le dipendenze cicliche tra le proprietà).
Non so se per più veloce intendi il modo più veloce per implementarlo o un codice che gira velocemente. Non dovresti ottimizzare prima di sapere se è necessario. L'ottimizzazione prematura è la radice di tutti i mali
IEquatable<T>un'implementazione si qualifichi come un caso di ottimizzazione prematura. La riflessione sarà drasticamente più lenta. L'implementazione predefinita di Equalsper i tipi di valore personalizzati utilizza la riflessione; Microsoft stessa consiglia di sovrascriverlo per le prestazioni: "Sostituisci il Equalsmetodo per un tipo particolare per migliorare le prestazioni del metodo e rappresentare più da vicino il concetto di uguaglianza per il tipo".
Serializza entrambi gli oggetti e confronta le stringhe risultanti con @JoelFan
Quindi, per fare ciò, crea una classe statica in questo modo e usa le estensioni per estendere TUTTI gli oggetti (così puoi passare qualsiasi tipo di oggetto, raccolta, ecc. Nel metodo)
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());
}
}
}
Dopo aver fatto riferimento a questa classe statica in qualsiasi altro file, puoi farlo:
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();
Ora puoi semplicemente usare .Equals per confrontarli. Lo uso per verificare se anche gli oggetti sono nelle raccolte. Funziona davvero bene.
CultrureInfo. Questo funzionerebbe solo se i dati interni sono principalmente stringhe e numeri interi. Altrimenti sarebbe un disastro.
Presumo che tu non ti stia riferendo letteralmente agli stessi oggetti
Object1 == Object2
Potresti pensare di fare un confronto della memoria tra i due
memcmp(Object1, Object2, sizeof(Object.GetType())
Ma non è nemmeno il codice reale in c # :). Poiché probabilmente tutti i dati vengono creati sull'heap, la memoria non è contigua e non è possibile confrontare l'uguaglianza di due oggetti in modo agnostico. Dovrai confrontare ogni valore, uno alla volta, in modo personalizzato.
Considera l'idea di aggiungere l' IEquatable<T>interfaccia alla tua classe e definire un Equalsmetodo personalizzato per il tuo tipo. Quindi, in quel metodo, testare manualmente ogni valore. Aggiungi di IEquatable<T>nuovo i tipi racchiusi se puoi e ripeti il processo.
class Foo : IEquatable<Foo>
{
public bool Equals(Foo other)
{
/* check all the values */
return false;
}
}
Serializza entrambi gli oggetti, quindi calcola il codice hash, quindi confronta.
Ho trovato questa funzione di seguito per confrontare gli oggetti.
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 sto usando e funziona bene per me.
ifsignifica ciò Compare(null, null) == falseche non è quello che mi aspetterei.
Sulla base di alcune risposte già fornite qui, ho deciso di sostenere principalmente la risposta di JoelFan . Adoro i metodi di estensione e questi hanno funzionato alla grande per me quando nessuna delle altre soluzioni li avrebbe utilizzati per confrontare le mie classi complesse.
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.");
Direi che:
Object1.Equals(Object2)
sarebbe quello che stai cercando. Questo se stai cercando di vedere se gli oggetti sono gli stessi, che è quello che sembra che tu stia chiedendo.
Se vuoi controllare se tutti gli oggetti figli sono uguali, eseguili attraverso un ciclo con il Equals()metodo.
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;
}
Un modo per farlo sarebbe ignorare Equals()ogni tipo coinvolto. Ad esempio, il tuo oggetto di primo livello sovrascriverà Equals()per chiamare il Equals()metodo di tutti e 5 gli oggetti figli. Anche questi oggetti dovrebbero sovrascrivere tutti Equals(), supponendo che siano oggetti personalizzati, e così via fino a quando l'intera gerarchia potrebbe essere confrontata semplicemente eseguendo un controllo di uguaglianza sugli oggetti di livello superiore.
Usa l' IEquatable<T>interfaccia che ha un metodo Equals.
Grazie all'esempio di Jonathan. L'ho espanso per tutti i casi (array, elenchi, dizionari, tipi primitivi).
Questo è un confronto senza serializzazione e non richiede l'implementazione di alcuna interfaccia per gli oggetti confrontati.
/// <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;
}
Per una facile copia del repository creato dal codice
Ora puoi usare json.net. Basta andare su Nuget e installarlo.
E puoi fare qualcosa del genere:
public bool Equals(SamplesItem sampleToCompare)
{
string myself = JsonConvert.SerializeObject(this);
string other = JsonConvert.SerializeObject(sampleToCompare);
return myself == other;
}
Potresti forse creare un metodo di estensione per object se vuoi diventare più elaborato. Si prega di notare che questo confronta solo le proprietà pubbliche. E se si desidera ignorare una proprietà pubblica quando si esegue il confronto, è possibile utilizzare l'attributo [JsonIgnore].