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
, если он реализует этот интерфейс.
Решение Liviu Trifoi: Использование библиотеки CompareNETObjects. GitHub - Пакет NuGet - Учебник .
Этот проект на codeplex проверяет почти любой тип свойства и может быть настроен по мере необходимости.
Вы никогда не хотите внедрять GetHashCode в изменяемые свойства (свойства, которые могут быть изменены кем-то), то есть не-частные сеттеры.
Представьте себе этот сценарий:
Угадайте, что ... ваш объект постоянно теряется в коллекции, так как коллекция использует GetHashCode (), чтобы найти его! Вы эффективно изменили значение hashcode из того, что изначально было размещено в коллекции. Наверное, не то, что вы хотели.
Мой путь к компилируемой версии дерева 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; }
}
Возможно, вам стоит взглянуть на 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);
}
Я думаю, что этот метод довольно опрятен, он избегает повторения или добавления чего-либо к классам. Что еще вы ищете?
Единственной альтернативой могло бы стать создание словаря состояния для старого и нового объектов и написать сравнение для них. Код для создания словаря состояния может повторно использовать любую сериализацию, которую вы имеете для хранения этих данных в базе данных.
Здесь короткая версия 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; }
}
}
Equals
? Я использую код, очень похожий на этот, и он никогда не дает ложных негативов. – Aaronaught 5 March 2010 в 20:19