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

Pet*_*son 55 .net c# reflection auditing

我正在处理的项目需要一些简单的审计日志记录,以便用户更改其电子邮件,帐单地址等.我们正在使用的对象来自不同的来源,一个是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;
}
Run Code Online (Sandbox Code Playgroud)

我正在寻找System.IComparable,因为"所有数字类型(如Int32和Double)都实现IComparable,String,Char和DateTime也是如此." 这似乎是找到任何不是自定义类的属性的最佳方法.

利用由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);
Run Code Online (Sandbox Code Playgroud)

"[地址] StateProvince从'MyAccountService.StateProvince'更改为'MyAccountService.StateProvince'"

它是StateProvince类的两个不同实例,但属性的值是相同的(在这种情况下都为null).我们没有凌驾于equals方法之上.

Aar*_*ght 26

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.
}
Run Code Online (Sandbox Code Playgroud)

这显然并不完美,但如果您只使用您控制的课程,那么您可以确保它始终适合您的特定需求.

还有其他比较对象的方法(例如校验和,序列化等),但如果类不能始终如一地实现IPropertyChanged并且您想要实际知道差异,则这可能是最可靠的.


更新新的示例代码:

Address address1 = new Address();
address1.StateProvince = new StateProvince();

Address address2 = new Address();
address2.StateProvince = new StateProvince();

IList list = Utility.GenerateAuditLogMessages(address1, address2);
Run Code Online (Sandbox Code Playgroud)

在使用的原因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");
Run Code Online (Sandbox Code Playgroud)

这些应该被视为平等吗?好吧,他们将使用您的方法,因为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);
    }
}
Run Code Online (Sandbox Code Playgroud)

完成此操作后,object.Equals代码将完美运行.它实际上会检查语义相等,而不是天真地检查是否address1address2字面上具有相同的StateProvince引用.


另一种方法是将跟踪代码扩展为实际下降到子对象.换句话说,对于每个属性,检查Type.IsClass和可选的Type.IsInterface属性,如果true,则递归调用属性本身的更改跟踪方法,为属性名称递归返回任何审计结果的前缀.所以你最终会改变StateProvinceCode.

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

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


bka*_*aid 18

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


Mik*_*Two 10

您可能希望查看Microsoft的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);
}
Run Code Online (Sandbox Code Playgroud)