查找两个C#对象之间的属性差异

我正在处理的项目需要一些简单的审计日志logging,以便用户更改他们的电子邮件,帐单地址等。我们正在使用的对象来自不同的来源,一个是WCF服务,另一个是Web服务。

我已经实现了使用reflection的以下方法来查找对两个不同对象的属性的更改。 这会生成一个与旧的和新的值有差异的属性列表。

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; } 

我正在寻找System.IComparable,因为“所有的数字types(如Int32和Double)都实现了IComparable,就像String,Char和DateTime一样。 这似乎是find任何不是自定义类的财产的最佳方式。

攻入由WCF或Web服务代理代码生成的PropertyChanged事件听起来不错,但没有给我足够的信息(我的审计日志(旧值和新值))。

寻找意见,如果有更好的方法来做到这一点,谢谢!

@Aaronaught,这里是一些基于做object.Equals生成积极匹配的示例代码:

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

“[Address] StateProvince从”MyAccountService.StateProvince“更改为”MyAccountService.StateProvince“”

这是StateProvince类的两个不同的实例,但属性的值是相同的(在这种情况下全为null)。 我们并不是重写equals方法。

IComparable是为了sorting比较。 或者使用IEquatable ,或者只使用静态的System.Object.Equals方法。 如果对象不是一个原始types,但是仍然可以工作,但是仍然通过覆盖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在这两种情况下可能都是空的,但是address1address2仍然具有StateProvince属性的非空值,并且每个实例都是不同的。 因此, address1address2具有不同的属性。

让我们翻转一下,以此代码为例:

 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 ,因此跟踪器只是完全跳过该属性。 但是这两个地址显然是不相等的!

这就是为什么我最初build议使用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代码将会完美的工作。 而不是简单地检查address1address2是否具有相同的StateProvince引用,它将实际检查语义是否相等。


另一种方式是扩展跟踪代码,以实际下降到子对象。 换句话说,对于每个属性,请检查Type.IsClass和可选的Type.IsInterface属性,如果为true ,则recursion地在属性本身上调用change-tracking方法,并以属性名称recursion返回所有审计结果的前缀。 所以你最终会改变StateProvinceCode

我有时候也使用上面的方法,但是更简单的方法是在要比较语义相等的对象(即审计)上覆盖Equals ,并提供一个合适的ToString重写,以清楚更改哪些内容。 这并不是为了进行深度嵌套而规模化的,但是我认为想以这种方式进行审计是非常不寻常的。

最后一个技巧是定义你自己的接口,比如IAuditable<T> ,它接受与参数相同types的第二个实例,实际返回所有差异的列表(或可枚举)。 它类似于我们重写的object.Equals方法,但是给出了更多的信息。 这对于对象图非常复杂并且您知道不能依赖于Reflection或Equals时非常有用。 你可以把这个和上面的方法结合起来。 真正所有你需要做的是替代你的IAuditable IComparable和调用Audit方法,如果它实现了该接口。

codeplex上的这个项目几乎可以检查任何types的属性,并且可以根据需要进行自定义。

你可能想看看微软的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); } 

你永远不想在可变属性上实现GetHashCode(可以被某个人改变的属性) – 也就是非私有的setter。

想象一下这个场景:

  1. 你把你的对象的一个​​实例放在一个使用GetHashCode()的集合中,或者直接使用(Hashtable)。
  2. 然后有人更改您在GetHashCode()实现中使用的字段/属性的值。

猜猜是什么…你的对象永久丢失在集合中,因为集合使用GetHashCode()来find它! 你已经有效地改变了哈希代码的价值从最初放在集合中。 可能不是你想要的。

我认为这个方法非常整洁,避免了重复或者向类中添加任何东西。 你还在找什么?

唯一的select是为旧的和新的对象生成状态字典,并为它们写一个比较。 用于生成状态字典的代码可以重新使用任何序列化来将这些数据存储在数据库中。

这里是一个简短的LINQ版本,它扩展了对象并返回了一个不相等的属性列表:

用法: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; } } }