Нахождение различий в свойстве между двумя объектами C#

Проект я работаю над потребностями некоторый простой аудит, регистрирующийся для того, когда пользователь изменяет их электронную почту, адрес выставления счета, и т.д. Объекты, с которыми мы работаем, прибывают из других источников, один сервис WCF, другой веб-сервис.

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

public static IList GenerateAuditLogMessages(T originalObject, T changedObject)
{
    IList list = new List();
    string className = string.Concat("[", originalObject.GetType().Name, "] ");

    foreach (PropertyInfo property in originalObject.GetType().GetProperties())
    {
        Type comparable =
            property.PropertyType.GetInterface("System.IComparable");

        if (comparable != null)
        {
            string originalPropertyValue =
                property.GetValue(originalObject, null) as string;
            string newPropertyValue =
                property.GetValue(changedObject, null) as string;

            if (originalPropertyValue != newPropertyValue)
            {
                list.Add(string.Concat(className, property.Name,
                    " changed from '", originalPropertyValue,
                    "' to '", newPropertyValue, "'"));
            }
        }
    }

    return list;
}

Я ищу Систему. IComparable, потому что "Все числовые типы (такие как Int32 и Дважды) реализуют IComparable, также, как и Строка, Символ и DateTime". Это казалось лучшим способом найти любое свойство, это не пользовательский класс.

Наслаждение событием PropertyChanged, это сгенерировано WCF или кодом прокси веб-сервиса, звучало хорошим, но не дает мне достаточно информации для моих контрольных журналов (старые и новые значения).

Поиск входа относительно того, если существует лучший способ сделать это, Спасибо!

@Aaronaught, вот некоторый пример кода, который генерирует положительное совпадение на основе выполнения объекта. Равняется:

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

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

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

"[Адрес] StateProvince, измененный от 'MyAccountService. StateProvince' к 'MyAccountService. StateProvince'"

Это - два различных экземпляра класса StateProvince, но значения свойств являются тем же (весь пустой указатель в этом случае). Мы не переопределяем, равняется методу.

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

4 ответа

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 ] свойство, и каждый экземпляр отличается. Следовательно, адрес1 и адрес2 имеют разные свойства.

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

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

26
ответ дан 7 November 2019 в 08:08
поделиться

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

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

0
ответ дан 7 November 2019 в 08:08
поделиться

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

18
ответ дан 7 November 2019 в 08:08
поделиться

Вы можете посмотреть на Microsoft's Testapi У него есть 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);
}
10
ответ дан 7 November 2019 в 08:08
поделиться
Другие вопросы по тегам:

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