Подтвердить что ты не робот

Лучший способ сравнить два сложных объекта

У меня есть два сложных объекта, например Object1 и Object2. Они имеют около 5 уровней дочерних объектов.

Мне нужен самый быстрый способ сказать, являются ли они одинаковыми или нет.

Как это можно сделать в С# 4.0?

4b9b3361

Ответ 1

Внедрите IEquatable<T> (обычно в сочетании с переопределением унаследованных методов Object.Equals и Object.GetHashCode) для всех ваших настраиваемых типов. В случае составных типов вызовите метод типов Equals пределах содержащихся типов. Для содержащихся коллекций используйте метод расширения SequenceEqual, который внутренне вызывает IEquatable<T>.Equals или Object.Equals для каждого элемента. Этот подход, очевидно, потребует от вас расширения типов определений типов, но его результаты быстрее, чем любые общие решения, включающие сериализацию.

Редактировать: Вот надуманный пример с тремя уровнями вложенности.

Для типов значений вы обычно можете просто вызвать свой метод Equals. Даже если поля или свойства никогда не были явно назначены, они все равно будут иметь значение по умолчанию.

Для ссылочных типов вы должны сначала вызвать ReferenceEquals, который проверяет ссылочное равенство - это будет служить повышением эффективности, когда вы будете ссылаться на один и тот же объект. Он также будет обрабатывать случаи, когда обе ссылки равны нулю. Если эта проверка NullReferenceException неудачно, подтвердите, что поле или свойство вашего экземпляра не равно null (чтобы избежать исключения NullReferenceException) и вызвать его метод Equals. Поскольку наши члены правильно набраны, метод IEquatable<T>.Equals напрямую, минуя переопределенный метод Object.Equals (выполнение которого будет незначительно медленнее из-за типа cast).

Когда вы переопределяете Object.Equals, вы также должны переопределить Object.GetHashCode; Я не делал этого ниже ради краткости.

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

Обновление: этот ответ был написан несколько лет назад. С тех пор я начал отходить от реализации IEquality<T> для изменяемых типов для таких сценариев. Существует два понятия равенства: тождество и эквивалентность. На уровне представления памяти они широко различаются как "ссылочное равенство" и "равенство ценности" (см. " Сравнение равенств"). Однако такое же различие может также применяться на уровне домена. Предположим, что ваш класс Person имеет свойство PersonId, уникальное для отдельного человека реального мира. Должны ли считаться равными или разными два объекта с одинаковыми значениями PersonId но разные значения Age? Вышеприведенный ответ предполагает, что один после эквивалентности. Однако существует много IEquality<T> интерфейса IEquality<T>, таких как коллекции, которые предполагают, что такие реализации обеспечивают идентификацию. Например, если вы TryGetValue(T,T) HashSet<T>, вы обычно ожидаете, что TryGetValue(T,T) будет возвращать существующие элементы, которые разделяют только личность вашего аргумента, не обязательно эквивалентные элементы, содержимое которых абсолютно одинаково. Это понятие соблюдается примечаниями в GetHashCode:

В общем случае для изменяемых типов ссылок вы должны переопределить GetHashCode() только в том случае, если:

  • Вы можете вычислить хэш-код из полей, которые не изменяются; или же
  • Вы можете убедиться, что хеш-код изменяемого объекта не изменяется, пока объект содержится в коллекции, которая полагается на свой хэш-код.

Ответ 2

Сериализовать оба объекта и сравнить полученные строки

Ответ 3

Вы можете использовать метод расширения, рекурсию для решения этой проблемы:

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

или сравните с помощью Json (если объект очень сложный) Вы можете использовать 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;
}

Ответ 4

Если вы не хотите реализовывать IEquatable, вы всегда можете использовать Reflection для сравнения всех свойств: - если они являются типом значения, просто сравните их -if, они являются ссылочным типом, вызовите функцию рекурсивно, чтобы сравнить ее "внутренний" свойства.

Я не думаю о производительности, а о простоте. Это зависит, однако, от точного дизайна ваших объектов. Это может осложниться в зависимости от формы ваших объектов (например, если есть циклические зависимости между свойствами). Однако есть несколько решений, которые вы можете использовать, например:

Другой вариант - сериализация объекта в виде текста, например, с использованием JSON.NET, и сравнение результатов сериализации. (JSON.NET может обрабатывать циклические зависимости между свойствами).

Я не знаю, если вы имеете в виду самый быстрый способ реализовать его или код, который работает быстро. Вы не должны оптимизировать, прежде чем знать, если вам нужно. Преждевременная оптимизация - корень всего зла

Ответ 5

Сериализуйте оба объекта и сравните полученные строки с помощью @JoelFan

Чтобы сделать это, создайте такой статический класс и используйте Extensions для расширения ВСЕХ объектов (чтобы вы могли передать любой тип объекта, коллекции и т.д. в метод)

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

Как только вы ссылаетесь на этот статический класс в любом другом файле, вы можете сделать это:

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();

Теперь вы можете просто использовать .Equals для их сравнения. Я использую это для проверки того, находятся ли объекты в коллекциях. Он работает очень хорошо.

Ответ 6

Я предполагаю, что вы не имеете в виду буквально те же объекты

Object1 == Object2

Возможно, вы думаете о сравнении памяти между двумя

memcmp(Object1, Object2, sizeof(Object.GetType())

Но это даже не настоящий код в С#:). Поскольку все ваши данные, вероятно, созданы в куче, память не смежна, и вы не можете просто сравнить равенство двух объектов агностически. Вам придется сравнивать каждое значение по одному за раз, по-своему.

Рассмотрим добавление интерфейса IEquatable<T> к вашему классу и определение настраиваемого метода Equals для вашего типа. Затем в этом методе вручную проверяется каждое значение. Добавьте IEquatable<T> снова в закрытые типы, если вы можете и повторите процесс.

class Foo : IEquatable<Foo>
{
  public bool Equals(Foo other)
  {
    /* check all the values */
    return false;
  }
}

Ответ 7

Я нашел эту функцию ниже для сравнения объектов.

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

Я использую его, и он отлично работает для меня.

Ответ 8

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

Ответ 9

Один из способов сделать это - переопределить Equals() для каждого используемого типа. Например, ваш объект верхнего уровня переопределит Equals(), чтобы вызвать метод Equals() для всех 5 дочерних объектов. Эти объекты должны переопределить также Equals(), предполагая, что они являются настраиваемыми объектами, и так далее, пока не сравните всю иерархию, просто выполнив проверку равенства на объектах верхнего уровня.

Ответ 10

Используйте IEquatable<T> Интерфейс, который имеет метод Equals.

Ответ 11

Основываясь на нескольких ответах, уже приведенных здесь, я решил в основном вернуть ответ JoelFan. Мне нравятся методы расширения, и они отлично подходят для меня, когда ни одно из других решений не использует их для сравнения моих сложных классов.

Методы расширения

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.");

Ответ 12

Благодаря примеру Джонатана. Я расширил его для всех случаев (массивы, списки, словари, примитивные типы).

Это сравнение без сериализации и не требует реализации каких-либо интерфейсов для сравниваемых объектов.

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

Для удобного копирования кода создан репозиторий

Ответ 13

Я бы сказал, что:

Object1.Equals(Object2)

будет тем, что вы ищете. Это, если вы хотите посмотреть, совпадают ли объекты, что вы, кажется, задаете.

Если вы хотите проверить, совпадают ли все дочерние объекты, запустите их через цикл с помощью метода Equals().

Ответ 14

Сериализуйте оба объекта, затем вычислите хэш-код, затем сравните.

Ответ 15

Очень простой способ:

  • Создайте список вашего объекта;
  • Если пересечение этих списков "ложно", то они равны. Простой:
  • Ссылка: using System.Linq;

    class Program
    {
        public class JohnsonObject
        {
            public int Id { get; set; }
            public string Description { get; set; }
        }
    
        static void Main()
        {
            JohnsonObject[] obj01 = new[] { new JohnsonObject { Id = 1, Description = "01" } };
            JohnsonObject[] obj02 = new[] { new JohnsonObject { Id = 1, Description = "01" } };
            Console.Write("Are equal? = {0}", !obj01.Intersect(obj02).Any());
        }
    }