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
, если он реализует этот интерфейс.