代码之家  ›  专栏  ›  技术社区  ›  Pete Nelson

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

  •  52
  • Pete Nelson  · 技术社区  · 16 年前

    我正在从事的项目需要一些简单的审核日志记录,以便在用户更改其电子邮件、帐单地址等时使用。我们正在使用的对象来自不同的来源,一个是WCF服务,另一个是web服务。

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

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

    寻找输入,如果有更好的方法来做这件事,谢谢!

    @Aaronaught,下面是一些基于doing对象生成正匹配的示例代码。等于:

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

    “[地址]更改自 'MyAccountService.StateProvince''

    它是StateProvince类的两个不同实例,但是属性的值是相同的(在本例中都为null)。我们没有重写equals方法。

    8 回复  |  直到 16 年前
        1
  •  28
  •   Aaronaught    16 年前

    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 对于 州省 属性,每个实例都是不同的。因此, 地址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");
    

    州省 不执行 I可比较 . 这就是您的方法报告两个对象在原始案例中是相同的唯一原因。自从 州省 类未实现 ,跟踪器完全跳过该属性。但这两个地址显然不相等!

    对象。等于 获得更好结果的方法:

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

    一旦你这么做了 对象。等于 代码将完美地工作。而不是天真地检查 地址1 和 地址2 州省 引用时,它实际上会检查语义是否相等。


    Type.IsClass Type.IsInterface 财产,如果 true ,然后递归调用属性本身的更改跟踪方法,在递归返回的任何审核结果前面加上属性名。所以你最终会有一个改变 StateProvinceCode .

    我有时也会使用上述方法,但只需重写就更容易了 等于 在要比较语义相等(即audit)的对象上,并提供适当的 ToString 覆盖,使它清楚什么改变了。它不适合深度嵌套,但我认为这样审计是不寻常的。

    IAuditable<T> ,它将同一类型的第二个实例作为参数,并实际返回所有差异的列表(或可枚举)。类似于我们的 对象。等于 等于 . 您可以将此与上述方法相结合;你要做的就是替换 为了你的 IAuditable 并调用 Audit

        2
  •  19
  •   Coden bkaid    5 年前

    This github上的项目几乎可以检查任何类型的属性,并且可以根据需要进行定制。

        3
  •  11
  •   Mike Two    16 年前

    你可能想看看 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);
    }
    
        4
  •  3
  •   Rick    9 年前

    以下是扩展对象并返回不相等属性列表的简短LINQ版本:

    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; }
            }
    
        }
    
        5
  •  2
  •   Dave Black    14 年前

    想象一下这个场景:

    1. 将对象的实例放在使用GetHashCode()“under the covers”或直接(Hashtable)的集合中。

    你猜怎么着…你的对象在集合中永久丢失了,因为集合使用GetHashCode()来找到它!您已经有效地更改了最初放置在集合中的hashcode值。可能不是你想要的。

        6
  •  1
  •   ali-myousefi    8 年前
        7
  •  0
  •   Phil H    16 年前

    我认为这个方法非常简洁,它避免了重复或向类中添加任何内容。你还要找什么?

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

        8
  •  0
  •   IlPADlI    8 年前

    我的生活方式 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; }
    }
    
    推荐文章