самый простой способ найти разницу двух объектов одного и того же класса [дубликат]

48
задан Pete Nelson 5 March 2010 в 20:44
поделиться

8 ответов

IComparable предназначен для упорядочения сравнений. Вместо этого используйте IEquatable или просто используйте статический метод System.Object.Equals. Последнее также имеет смысл работать, если объект не является примитивным типом, но все же определяет его собственное сравнение равенства путем переопределения Equals.

object originalValue = property.GetValue(originalObject, null);
object newValue = property.GetValue(changedObject, null);
if (!object.Equals(originalValue, newValue))
{
    string originalText = (originalValue != null) ?
        originalValue.ToString() : "[NULL]";
    string newText = (newText != null) ?
        newValue.ToString() : "[NULL]";
    // etc.
}

Это, очевидно, не идеально,

Существуют и другие методы для сравнения объектов (например, контрольные суммы, сериализация и т. д.), но это означает, что вы используете только те классы, которые вы контролируете, тогда вы можете убедиться, что он всегда работает для ваших конкретных потребностей. вероятно, самый надежный, если классы не реализуют IPropertyChanged последовательно, и вы действительно хотите знать различия.


Обновить для нового примера код:

Address address1 = new Address();
address1.StateProvince = new StateProvince();

Address address2 = new Address();
address2.StateProvince = new StateProvince();

IList list = Utility.GenerateAuditLogMessages(address1, address2);

Причина, по которой использование object.Equals в вашем методе аудита приводит к «удару», заключается в том, что экземпляры на самом деле не равны!

Конечно, StateProvince может быть пустым в обоих случаях, но address1 и address2 по-прежнему имеют ненулевые значения для свойства StateProvince, и каждый экземпляр отличается. Следовательно, address1 и address2 имеют разные свойства.

Давайте перевернем это вокруг, возьмите этот код в качестве примера:

Address address1 = new Address("35 Elm St");
address1.StateProvince = new StateProvince("TX");

Address address2 = new Address("35 Elm St");
address2.StateProvince = new StateProvince("AZ");

Должны ли они считаться равными? Ну, они будут, используя ваш метод, потому что StateProvince не реализует IComparable. Это единственная причина, по которой ваш метод сообщил, что оба объекта были одинаковыми в исходном случае. Поскольку класс StateProvince не реализует IComparable, трекер просто пропускает это свойство полностью. Но эти два адреса явно не равны!

Вот почему я изначально предложил использовать object.Equals, потому что тогда вы можете переопределить его в методе StateProvince, чтобы получить лучшие результаты:

public class StateProvince
{
    public string Code { get; set; }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        StateProvince sp = obj as StateProvince;
        if (object.ReferenceEquals(sp, null))
            return false;

        return (sp.Code == Code);
    }

    public bool Equals(StateProvince sp)
    {
        if (object.ReferenceEquals(sp, null))
            return false;

        return (sp.Code == Code);
    }

    public override int GetHashCode()
    {
        return Code.GetHashCode();
    }

    public override string ToString()
    {
        return string.Format("Code: [{0}]", Code);
    }
}

Как только вы это сделаете, код object.Equals будет работать отлично. Вместо того, чтобы наивно проверять, имеют ли address1 и address2 буквально одну и ту же ссылку StateProvince, она фактически проверяет семантическое равенство.


. Другой способ - расширить код отслеживания для фактического спуска в под-объекты. Другими словами, для каждого свойства проверьте свойство Type.IsClass и опцию Type.IsInterface, а если true, то рекурсивно вызовите метод отслеживания изменений в самом свойстве, префикс любых результатов аудита, возвращаемых рекурсивно с именем свойства , Таким образом, вы закончите с изменением для StateProvinceCode.

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

Последний трюк состоит в том, чтобы определить ваш собственный интерфейс, скажем IAuditable<T>, который принимает второй экземпляр того же введите в качестве параметра и фактически вернет список (или перечислимый) всех различий. Это похоже на наш переопределенный метод object.Equals выше, но возвращает дополнительную информацию. Это полезно, когда граф объектов действительно сложный, и вы знаете, что не можете полагаться на Reflection или Equals. Вы можете комбинировать это с вышеуказанным подходом; действительно все, что вам нужно сделать, это заменить IComparable на ваш IAuditable и вызвать метод Audit, если он реализует этот интерфейс.

23
ответ дан Aaronaught 23 August 2018 в 22:35
поделиться
  • 1
    К сожалению, используя только object.Equals возвращает true для ссылочных типов, например: [Address] StateProvince изменен с «MyAccountService.StateProvince» на «MyAccountService.StateProvince» – Pete Nelson 5 March 2010 в 20:14
  • 2
    @Pete Nelson: Предполагая, что вы на самом деле сравниваете разные ссылки, это ... невероятно. Можем ли мы увидеть полный пример с классом? Переопределяет ли это метод Equals? Я использую код, очень похожий на этот, и он никогда не дает ложных негативов. – Aaronaught 5 March 2010 в 20:19
  • 3
    Добавлен пример совпадения с исходным сообщением. – Pete Nelson 5 March 2010 в 20:44
  • 4
    Это определенно приемлемое решение для большинства ситуаций. Тем не менее, мы работаем с прокси-серверами WCF и веб-сервисов, поэтому реализация интерфейсов или переопределения эквивалентов не так проста. Поскольку сгенерированные классы прокси объявляются как частичные классы, и мы можем использовать дополнительный код, это не невозможно. Я определенно собираюсь проверить свойство Type.IsClass, хотя и посмотреть, хочу ли я спуститься в эту кроличью дыру при сравнении всего графа объектов. – Pete Nelson 5 March 2010 в 23:39

Решение Liviu Trifoi: Использование библиотеки CompareNETObjects. GitHub - Пакет NuGet - Учебник .

1
ответ дан ali-myousefi 23 August 2018 в 22:35
поделиться

Этот проект на codeplex проверяет почти любой тип свойства и может быть настроен по мере необходимости.

18
ответ дан bkaid 23 August 2018 в 22:35
поделиться
  • 1
    Из некоторых беглых тестов это тоже выглядит неплохо. По крайней мере, их исходный код дает мне больше информации о том, как они проводят сравнение объектов. – Pete Nelson 5 March 2010 в 23:49
  • 2
    спасибо за ссылку thekaido, вы спасли меня много времени, реализуя аналогичную библиотеку самостоятельно :) – Ryan Barrett 1 July 2010 в 11:47
  • 3
    Вот еще один, который использует деревья выражений. Наверное, быстрее. github.com/StevenGilligan/AutoCompare – Damien Sawyer 12 September 2017 в 23:41

Вы никогда не хотите внедрять GetHashCode в изменяемые свойства (свойства, которые могут быть изменены кем-то), то есть не-частные сеттеры.

Представьте себе этот сценарий:

  1. вы помещаете экземпляр своего объекта в коллекцию, которая использует GetHashCode () «под обложками» или напрямую (Hashtable).
  2. Затем кто-то изменяет значение поля / свойства, которое вы использовали в реализации GetHashCode ().

Угадайте, что ... ваш объект постоянно теряется в коллекции, так как коллекция использует GetHashCode (), чтобы найти его! Вы эффективно изменили значение hashcode из того, что изначально было размещено в коллекции. Наверное, не то, что вы хотели.

2
ответ дан Dave Black 23 August 2018 в 22:35
поделиться

Мой путь к компилируемой версии дерева Expression. Он должен быть быстрее, чем PropertyInfo.GetValue.

static class ObjDiffCollector<T>
{
    private delegate DiffEntry DiffDelegate(T x, T y);

    private static readonly IReadOnlyDictionary<string, DiffDelegate> DicDiffDels;

    private static PropertyInfo PropertyOf<TClass, TProperty>(Expression<Func<TClass, TProperty>> selector)
        => (PropertyInfo)((MemberExpression)selector.Body).Member;

    static ObjDiffCollector()
    {
        var expParamX = Expression.Parameter(typeof(T), "x");
        var expParamY = Expression.Parameter(typeof(T), "y");

        var propDrName = PropertyOf((DiffEntry x) => x.Prop);
        var propDrValX = PropertyOf((DiffEntry x) => x.ValX);
        var propDrValY = PropertyOf((DiffEntry x) => x.ValY);

        var dic = new Dictionary<string, DiffDelegate>();

        var props = typeof(T).GetProperties();
        foreach (var info in props)
        {
            var expValX = Expression.MakeMemberAccess(expParamX, info);
            var expValY = Expression.MakeMemberAccess(expParamY, info);

            var expEq = Expression.Equal(expValX, expValY);

            var expNewEntry = Expression.New(typeof(DiffEntry));
            var expMemberInitEntry = Expression.MemberInit(expNewEntry,
                Expression.Bind(propDrName, Expression.Constant(info.Name)),
                Expression.Bind(propDrValX, Expression.Convert(expValX, typeof(object))),
                Expression.Bind(propDrValY, Expression.Convert(expValY, typeof(object)))
            );

            var expReturn = Expression.Condition(expEq
                , Expression.Convert(Expression.Constant(null), typeof(DiffEntry))
                , expMemberInitEntry);

            var expLambda = Expression.Lambda<DiffDelegate>(expReturn, expParamX, expParamY);

            var compiled = expLambda.Compile();

            dic[info.Name] = compiled;
        }

        DicDiffDels = dic;
    }

    public static DiffEntry[] Diff(T x, T y)
    {
        var list = new List<DiffEntry>(DicDiffDels.Count);
        foreach (var pair in DicDiffDels)
        {
            var r = pair.Value(x, y);
            if (r != null) list.Add(r);
        }
        return list.ToArray();
    }
}

class DiffEntry
{
    public string Prop { get; set; }
    public object ValX { get; set; }
    public object ValY { get; set; }
}
0
ответ дан IlPADlI 23 August 2018 в 22:35
поделиться

Возможно, вам стоит взглянуть на Testapi от Microsoft . У него есть сравнение объектов api, которое делает глубокие сравнения. Это может быть излишним для вас, но это может стоить взгляда.

var comparer = new ObjectComparer(new PublicPropertyObjectGraphFactory());
IEnumerable<ObjectComparisonMismatch> mismatches;
bool result = comparer.Compare(left, right, out mismatches);

foreach (var mismatch in mismatches)
{
    Console.Out.WriteLine("\t'{0}' = '{1}' and '{2}'='{3}' do not match. '{4}'",
        mismatch.LeftObjectNode.Name, mismatch.LeftObjectNode.ObjectValue,
        mismatch.RightObjectNode.Name, mismatch.RightObjectNode.ObjectValue,
        mismatch.MismatchType);
}
9
ответ дан Mike Two 23 August 2018 в 22:35
поделиться

Я думаю, что этот метод довольно опрятен, он избегает повторения или добавления чего-либо к классам. Что еще вы ищете?

Единственной альтернативой могло бы стать создание словаря состояния для старого и нового объектов и написать сравнение для них. Код для создания словаря состояния может повторно использовать любую сериализацию, которую вы имеете для хранения этих данных в базе данных.

0
ответ дан Phil H 23 August 2018 в 22:35
поделиться

Здесь короткая версия LINQ, которая расширяет объект и возвращает список свойств, которые не равны:

use: object.DetailedCompare (objectToCompare);

public static class ObjectExtensions
    {

        public static List<Variance> DetailedCompare<T>(this T val1, T val2)
        {
            var propertyInfo = val1.GetType().GetProperties();
            return propertyInfo.Select(f => new Variance
                {
                    Property = f.Name,
                    ValueA = f.GetValue(val1),
                    ValueB = f.GetValue(val2)
                })
                .Where(v => !v.ValueA.Equals(v.ValueB))
                .ToList();
        }

        public class Variance
        {
            public string Property { get; set; }
            public object ValueA { get; set; }
            public object ValueB { get; set; }
        }

    }
2
ответ дан Rick 23 August 2018 в 22:35
поделиться
  • 1
    Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, как и / или почему оно решает проблему, улучшит долгосрочную ценность ответа. Пожалуйста, прочтите это как ответить за предоставление качественного ответа. – thewaywewere 26 June 2017 в 13:21
  • 2
    Это работает только для базовых типов свойств. Списки дочерних объектов всегда будут отображаться как разные, даже если они одинаковы. – Alan Barber 13 July 2017 в 15:28
Другие вопросы по тегам:

Похожие вопросы: