Najlepszy sposób na porównanie dwóch złożonych obiektów


112

Mam dwa złożone obiekty, takie jak Object1i Object2. Mają około 5 poziomów obiektów podrzędnych.

Potrzebuję najszybszej metody, aby stwierdzić, czy są takie same, czy nie.

Jak można to zrobić w C # 4.0?

Odpowiedzi:


101

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.

Otrzymuję ten obiekt za pośrednictwem usług RIA ... Czy mogę użyć IEquatable <Foo> dla tych obiektów i pobrać go do klienta WPF?
Deweloper

1
Masz na myśli, że klasy są generowane automatycznie? Nie korzystałem z usług RIA, ale zakładałbym, że wszelkie wygenerowane klasy zostaną zadeklarowane jako 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.
Douglas

A jeśli „Adres adresowy” to w rzeczywistości „Adres [] adresy”, jak zostałby zaimplementowany?
guiomie

2
Można zadzwonić do LINQ 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.
Douglas

2
Inną kategorią porównań, które może sprawdzić programista, jest „WorksLike”. Dla mnie oznacza to, że nawet jeśli dwie instancje mogą mieć nierówne wartości właściwości, program da ten sam wynik po przetworzeniu dwóch instancji.
John Kurtz,

95

Serializuj oba obiekty i porównaj otrzymane ciągi


1
Nie rozumiem, dlaczego miałoby to być. Serializacja jest zwykle procesem zoptymalizowanym i w każdym przypadku musisz uzyskać dostęp do wartości każdej właściwości.
JoelFan

5
Koszt jest ogromny. Generujesz strumień danych, dołączasz ciągi, a następnie testujesz równość ciągów. Rzędy wielkości, tylko w tym. Nie wspominając o tym, że serializacja będzie domyślnie używać odbicia.
Jerome Haltom

2
Strumień danych to nic wielkiego, nie rozumiem, dlaczego miałbyś dołączać ciągi znaków ... testowanie równości ciągów jest jedną z najbardziej zoptymalizowanych operacji ... możesz mieć rację z refleksją ... ale na cała serializacja nie będzie „o rząd wielkości” gorsza niż inne metody. Powinieneś zrobić testy porównawcze, jeśli podejrzewasz problemy z wydajnością ... Nie doświadczyłem problemów z wydajnością przy tej metodzie
JoelFan

12
Jestem +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.
Thomas

1
To nie jest dobre rozwiązanie, ponieważ obie serializacje mogą się nie udać w podobny sposób. Na przykład niektóre właściwości obiektu źródłowego mogły nie zostać serializowane, a po deserializacji zostaną ustawione na null w obiekcie docelowym. W takim przypadku test porównujący łańcuchy przejdzie pomyślnie, ale oba obiekty w rzeczywistości nie są takie same!
stackMeUp

35

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;
}

1
Pierwsze rozwiązanie jest świetne! Podoba mi się, że nie musisz serializować json ani implementować dodawania kodu do samych obiektów. Odpowiednie, gdy porównujesz tylko testy jednostkowe. Czy mogę zasugerować dodanie prostego porównania w przypadku, gdy objValue i anotherValue są równe null? Pozwoli to uniknąć wyrzucenia wyjątku NullReferenceException podczas próby wykonania null.Equals () // ReSharper wyłącza się po RedundantJumpStatement if (objValue == anotherValue) continue; // zerowe odwołanie strażnik else if (! objValue.Equals (anotherValue)) Fail (oczekiwany, rzeczywisty);
Mark Conway

3
Czy jest jakiś powód, aby używać DeepComparezamiast po prostu CompareExrekurencyjnego wywoływania ?
apostolov

3
Może to niepotrzebnie porównać całą konstrukcję. Wymiana resultz return falsestałaby się bardziej efektywne.
Tim Sylvester

24

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


1
Nie wydaje mi się, żeby 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”.
Douglas

1
To zależy od tego, ile razy użyje metody równości: 1, 10, 100, 100, milion? To zrobi wielką różnicę. Jeśli będzie mógł użyć ogólnego rozwiązania bez implementacji czegokolwiek, zaoszczędzi trochę cennego czasu. Jeśli jest zbyt wolny, to czas na zaimplementowanie IEquatable (a może nawet próbę stworzenia buforowalnego lub inteligentnego GetHashCode) Jeśli chodzi o prędkość odbicia, muszę się zgodzić, że jest wolniejsza ... lub znacznie wolniejsza, w zależności od tego, jak to robisz ( tj. ponowne wykorzystanie typów PropertyInfos i tak dalej, lub nie).
JotaBe

@ Worthy7 Tak. Zapraszamy do zapoznania się z treścią projektu. Testy są dobrym sposobem dokumentowania na przykładach. Ale lepiej, jeśli go poszukasz, znajdziesz plik pomocy .chm. Więc ten projekt ma znacznie lepszą dokumentację niż większość projektów.
JotaBe

Przepraszam, masz rację, całkowicie przegapiłem zakładkę „wiki”. Jestem przyzwyczajony do tego, że wszyscy piszą rzeczy w pliku readme.
Worthy7

9

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.


A co, jeśli zawartość obiektów to tablice liczb zmiennoprzecinkowych. Konwertowanie ich na ciągi jest bardzo nieefektywne, a konwersja podlega przekształceniom zdefiniowanym w 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.
John Alexiou,

3
A co by było, gdyby nowy reżyser powiedział ci, żebyś wyrwał C # i zastąpił go Pythonem. Jako programiści musimy się nauczyć, że pytania typu „co jeśli” muszą się gdzieś zatrzymać. Rozwiąż problem, przejdź do następnego. Jeśli kiedykolwiek będziesz miał czas, wróć do niego ...
ozzy432836

2
Python jest bardziej podobny do MATLAB-a pod względem składni i użycia. Musi istnieć naprawdę dobry powód, aby przejść od bezpiecznego języka statycznego do skryptu miszmaszowego, takiego jak python.
John Alexiou,

5

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;
  }
}


3

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.


1
Pierwsza ifoznacza Compare(null, null) == falseto, czego bym się nie spodziewał.
Tim Sylvester

3

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.

Metody rozszerzeń

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;
    }

}

Przykłady użycia

if (record.IsBlank())
    throw new Exception("Record found is blank.");

if (record.EqualTo(new record()))
    throw new Exception("Record found is blank.");

2

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.


2
Jeśli i tylko wtedy, gdy zapewniają przeciążenie równości bez odwołań równe.
user7116

Każda klasa musi implementować własny sposób porównania. Jeśli klasy autorskie nie mają przesłonięć dla metody Equals (), użyją podstawowej metody klasy System.Object (), co doprowadzi do błędów w logice.
Dima

2
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;
}

Korzystanie z zasad rekursji.
matan justme

1

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.



1

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


1

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].


Jeśli masz listy w swoich obiektach, a te mają listy, wtedy próba przejścia przez oba obiekty będzie koszmarem. Jeśli serializujesz oba, a następnie porównasz, nie będziesz musiał radzić sobie z tym scenariuszem.
ashlar64

Jeśli twój złożony obiekt zawiera słownik, nie sądzę, aby serializator .net mógł go serializować. Serializator Json może.
ashlar64
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.