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