Odpowiedzi:
Zaimplementuj IEquatable<T>(zwykle w połączeniu z zastępowaniem dziedziczonych Object.Equalsi Object.GetHashCodemetod) we wszystkich typach niestandardowych. W przypadku typów złożonych wywołaj Equalsmetodę typów zawartych w typach zawierających. W przypadku kolekcji zawartych użyj SequenceEqualmetody rozszerzenia, która wewnętrznie wywołuje IEquatable<T>.Equalslub Object.Equalsna każdym elemencie. To podejście będzie oczywiście wymagało rozszerzenia definicji typów, ale jego wyniki są szybsze niż w przypadku jakichkolwiek ogólnych rozwiązań obejmujących serializację.
Edycja : Oto wymyślony przykład z trzema poziomami zagnieżdżenia.
W przypadku typów wartości zwykle można po prostu wywołać ich Equalsmetodę. Nawet jeśli pola lub właściwości nigdy nie zostały jawnie przypisane, nadal miałyby wartość domyślną.
W przypadku typów referencyjnych należy najpierw wywołać ReferenceEquals, które sprawdza równość odwołań - posłuży to jako zwiększenie wydajności, gdy zdarzy się odwołać do tego samego obiektu. Obsługuje również przypadki, w których oba odwołania są zerowe. Jeśli to sprawdzenie się nie powiedzie, potwierdź, że pole lub właściwość instancji nie ma wartości null (aby uniknąć NullReferenceException) i wywołaj jej Equalsmetodę. Ponieważ nasi członkowie są poprawnie wpisani, IEquatable<T>.Equalsmetoda jest wywoływana bezpośrednio, z pominięciem Object.Equalsmetody przesłoniętej (której wykonanie byłoby nieznacznie wolniejsze ze względu na rzutowanie typu).
Kiedy nadpisujesz Object.Equals, oczekuje się również, że nadpisujesz Object.GetHashCode; Nie zrobiłem tego poniżej ze względu na zwięzł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);
}
}
Aktualizacja : Ta odpowiedź została napisana kilka lat temu. Od tego czasu zacząłem odchylać się od implementowania IEquality<T>typów zmiennych dla takich scenariuszy. Istnieją dwa pojęcia równości: tożsamość i równoważność . Na poziomie reprezentacji pamięci są one powszechnie rozróżniane jako „równość odniesień” i „równość wartości” (zobacz Porównanie równości ). Jednak to samo rozróżnienie można również zastosować na poziomie domeny. Załóżmy, że twoja Personklasa ma PersonIdwłaściwość unikalną dla każdej osoby ze świata rzeczywistego. Czy dwa obiekty o takich samych, PersonIdale różnych Agewartościach należy uznać za równe czy różne? W powyższej odpowiedzi zakłada się, że chodzi o równoważność. Jednak istnieje wiele zastosowańIEquality<T>interfejs, na przykład kolekcje, które zakładają, że takie implementacje zapewniają tożsamość . Na przykład, jeśli wypełniasz element a HashSet<T>, zwykle spodziewasz się, że TryGetValue(T,T)wywołanie zwróci istniejące elementy, które mają tylko tożsamość twojego argumentu, niekoniecznie równoważne elementy, których zawartość jest całkowicie taka sama. Narzucają to uwagi dotyczące GetHashCode:
Ogólnie rzecz biorąc, w przypadku zmiennych typów odwołań należy nadpisywać
GetHashCode()tylko wtedy, gdy:
- Możesz obliczyć kod skrótu z pól, które nie są modyfikowalne; lub
- Można zapewnić, że kod skrótu obiektu zmiennego nie zmienia się, gdy obiekt znajduje się w kolekcji, która opiera się na jego kodzie skrótu.
partial- w takim przypadku tak, możesz zaimplementować ich Equalsmetodę poprzez ręcznie dodaną deklarację klasy częściowej, która odwołuje się do pól / właściwości z automatycznie wygenerowanego jeden.
Enumerable.SequenceEqualmetody na tablicach: this.Addresses.SequenceEqual(other.Addresses). Spowoduje to wewnętrzne wywołanie Address.Equalsmetody dla każdej pary odpowiednich adresów, biorąc pod uwagę, że Addressklasa implementuje IEquatable<Address>interfejs.
Serializuj oba obiekty i porównaj otrzymane ciągi
+1tym po prostu dlatego, że nigdy nie myślałem o przeprowadzaniu w ten sposób opartego na wartościach porównania równości. To ładne i proste. Byłoby fajnie zobaczyć kilka testów porównawczych z tym kodem.
Możesz użyć metody rozszerzenia, rekurencji, aby rozwiązać ten problem:
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;
}
lub porównaj za pomocą Json (jeśli obiekt jest bardzo złożony) Możesz użyć 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;
}
DeepComparezamiast po prostu CompareExrekurencyjnego wywoływania ?
resultz return falsestałaby się bardziej efektywne.
Jeśli nie chcesz implementować IEquatable, zawsze możesz użyć Reflection, aby porównać wszystkie właściwości: - jeśli są typami wartości, po prostu je porównaj - jeśli są typami referencyjnymi, wywołaj funkcję rekurencyjnie, aby porównać jej "wewnętrzne" właściwości .
Nie myślę o wydajności, ale o prostocie. Zależy to jednak od dokładnego projektu twoich obiektów. Może się to skomplikować w zależności od kształtu obiektów (na przykład, jeśli między właściwościami występują cykliczne zależności). Istnieje jednak kilka rozwiązań, z których możesz skorzystać, na przykład to:
Inną opcją jest serializacja obiektu jako tekst, na przykład przy użyciu JSON.NET, i porównanie wyniku serializacji. (JSON.NET może obsługiwać cykliczne zależności między właściwościami).
Nie wiem, czy przez najszybszy masz na myśli najszybszy sposób na wdrożenie, czy kod, który działa szybko. Nie powinieneś optymalizować, zanim nie dowiesz się, czy musisz. Przedwczesna optymalizacja jest źródłem wszelkiego zła
IEquatable<T>implementacja kwalifikowała się jako przypadek przedwczesnej optymalizacji. Odbicie będzie drastycznie wolniejsze. Domyślna implementacja Equalsdla niestandardowych typów wartości używa odbicia; Firma Microsoft sama zaleca zastąpienie jej Equalsw celu zwiększenia wydajności: „Zastąp metodę dla określonego typu, aby poprawić wydajność metody i dokładniej reprezentować koncepcję równości dla typu”.
Serializuj oba obiekty i porównaj wynikowe ciągi przez @JoelFan
Aby to zrobić, utwórz klasę statyczną, taką jak ta i użyj rozszerzeń, aby rozszerzyć WSZYSTKIE obiekty (aby można było przekazać do metody dowolny typ obiektu, kolekcji itp.)
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());
}
}
}
Gdy już odniesiesz się do tej klasy statycznej w jakimkolwiek innym pliku, możesz to zrobić:
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();
Teraz możesz po prostu użyć .Equals, aby je porównać. Używam tego do sprawdzania, czy obiekty są również w kolekcjach. Działa naprawdę dobrze.
CultrureInfo. Działa to tylko wtedy, gdy dane wewnętrzne to głównie łańcuchy i liczby całkowite. W przeciwnym razie byłaby to katastrofa.
Zakładam, że nie masz na myśli dosłownie tych samych obiektów
Object1 == Object2
Być może zastanawiasz się nad porównaniem pamięci między nimi
memcmp(Object1, Object2, sizeof(Object.GetType())
Ale to nie jest nawet prawdziwy kod w c # :). Ponieważ wszystkie dane są prawdopodobnie tworzone na stercie, pamięć nie jest ciągła i nie można po prostu porównać równości dwóch obiektów w sposób agnostyczny. Będziesz musiał porównać każdą wartość, pojedynczo, w niestandardowy sposób.
Rozważ dodanie IEquatable<T>interfejsu do swojej klasy i zdefiniuj Equalsmetodę niestandardową dla swojego typu. Następnie w tej metodzie ręcznie przetestuj każdą wartość. IEquatable<T>Jeśli możesz, dodaj ponownie załączone typy i powtórz proces.
class Foo : IEquatable<Foo>
{
public bool Equals(Foo other)
{
/* check all the values */
return false;
}
}
Serializuj oba obiekty, a następnie oblicz kod skrótu, a następnie porównaj.
Znalazłem tę funkcję do porównywania obiektów.
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;
}
Używam go i działa dobrze dla mnie.
ifoznacza Compare(null, null) == falseto, czego bym się nie spodziewał.
Opierając się na kilku odpowiedziach już tu udzielonych, zdecydowałem się w większości poprzeć odpowiedź JoelFana . Uwielbiam metody rozszerzające i te działały świetnie, gdy żadne inne rozwiązania nie używały ich do porównywania moich złożonych klas.
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.");
Powiedziałbym to:
Object1.Equals(Object2)
byłoby tym, czego szukasz. Dzieje się tak, jeśli chcesz sprawdzić, czy obiekty są takie same, o co wydajesz się pytać.
Jeśli chcesz sprawdzić, czy wszystkie obiekty podrzędne są takie same, przeprowadź je przez pętlę za pomocą Equals()metody.
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;
}
Jednym ze sposobów byłoby nadpisanie Equals()każdego typu. Na przykład, twój obiekt najwyższego poziomu przesłoniłby Equals()wywołanie Equals()metody wszystkich 5 obiektów podrzędnych. Wszystkie te obiekty powinny również nadpisywać Equals(), zakładając, że są to obiekty niestandardowe, i tak dalej, aż cała hierarchia będzie mogła zostać porównana, po prostu wykonując sprawdzenie równości na obiektach najwyższego poziomu.
Użyj IEquatable<T>interfejsu, który ma metodę Equals.
Dzięki przykładowi Jonathana. Rozszerzyłem go na wszystkie przypadki (tablice, listy, słowniki, typy pierwotne).
Jest to porównanie bez serializacji i nie wymaga implementacji żadnych interfejsów dla porównywanych obiektów.
/// <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;
}
Do łatwego kopiowania kodu utworzonego repozytorium
Możesz teraz korzystać z json.net. Po prostu przejdź na Nuget i zainstaluj go.
Możesz zrobić coś takiego:
public bool Equals(SamplesItem sampleToCompare)
{
string myself = JsonConvert.SerializeObject(this);
string other = JsonConvert.SerializeObject(sampleToCompare);
return myself == other;
}
Być może mógłbyś stworzyć metodę rozszerzającą dla obiektu, jeśli chcesz uzyskać bardziej wyrafinowany wygląd. Należy pamiętać, że jest to tylko porównanie właściwości publicznych. A jeśli chcesz zignorować właściwość publiczną podczas porównywania, możesz użyć atrybutu [JsonIgnore].