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

查找两个C#对象之间的属性差异,c#,.net,reflection,auditing,C#,.net,Reflection,Auditing,我正在从事的项目需要一些简单的审核日志记录,以便用户更改其电子邮件、账单地址等。我们正在处理的对象来自不同的来源,一个是WCF服务,另一个是web服务 我使用反射实现了以下方法,以查找对两个不同对象的属性的更改。这将生成一个有差异的属性列表,以及它们的旧值和新值 public static IList GenerateAuditLogMessages(T originalObject, T changedObject) { IList list = new List(); str

我正在从事的项目需要一些简单的审核日志记录,以便用户更改其电子邮件、账单地址等。我们正在处理的对象来自不同的来源,一个是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;
}
我正在寻找System.IComparable,因为“所有数字类型(如Int32和Double)都实现IComparable,String、Char和DateTime也是如此。”这似乎是找到任何非自定义类的属性的最佳方法

利用WCF或web服务代理代码生成的PropertyChanged事件听起来不错,但没有为我的审核日志(新旧值)提供足够的信息

寻找输入,如果有更好的方法来做到这一点,谢谢

@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”到 “MyAccountService.StateProvince”

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

您可能想看看它有一个进行深度比较的对象比较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);
}
var comparer=new ObjectComparer(new PublicPropertyObjectGraphFactory());
i数不清的不匹配;
bool result=comparer.Compare(左、右、外不匹配);
foreach(不匹配中的var不匹配)
{
Console.Out.WriteLine(“\t'{0}'='{1}'和'{2}'='{3}'不匹配。'{4}'”,
不匹配.LeftObjectNode.Name,不匹配.LeftObjectNode.ObjectValue,
不匹配.RightObjectNode.Name,不匹配.RightObjectNode.ObjectValue,
不匹配。不匹配类型);
}

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


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

IComparable
用于排序比较。可以使用
IEquatable
,也可以只使用static
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);
    }
}
完成此操作后,
对象.Equals
代码将完美工作。与其天真地检查
address1
address2
是否具有相同的
StateProvince
引用,它实际上会检查语义平等性


另一种方法是扩展跟踪代码,使其实际下降到子对象中。换句话说,对于每个属性,检查
Type.IsClass
和可选的
Type.IsInterface
属性,如果
true
,则递归调用属性本身的更改跟踪方法,在递归返回的任何审计结果前面加上属性名称。因此,您最终将更改
StateProvinceCode

我有时也会使用上述方法,但更容易的方法是在要比较语义相等的对象(即审计)上覆盖
Equals
,并提供适当的
ToString
覆盖,以明确更改的内容。它不适合深度嵌套,但我认为这样审计是不寻常的

最后一个技巧是定义自己的接口,比如说
IAuditable
,它将同一类型的第二个实例作为参数,并实际返回所有差异的列表(或可枚举)。它类似于我们覆盖的
对象。等于上面的
方法,但会返回更多信息。当对象图非常复杂,并且您知道不能依赖反射或
等于时,这非常有用。您可以将其与上述方法相结合;实际上,你所要做的就是替换
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; }
        }

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