发现两个C#对象之间的性质差异 [英] Finding property differences between two C# objects

查看:214
本文介绍了发现两个C#对象之间的性质差异的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的工作需要,当用户更改他们的电子邮件,账单地址等。我们正在与来自不同来源传来的对象,一个WCF服务,另一个是网络对于一些简单的审计记录的项目服务。

我实现使用反射来找到改变性质上两个不同的对象的以下的方法。这产生具有连同其新旧值差异的属性的列表。

 公共静态的IList GenerateAuditLogMessages(T originalObject,T changedObject)
{
    IList的列表=新名单();
    字符串的className = string.Concat([,originalObject.GetType()名。]);

    的foreach(的PropertyInfo财产originalObject.GetType()。GetProperties中())
    {
        类型可比=
            property.PropertyType.GetInterface(System.IComparable);

        如果(相当于!= NULL)
        {
            串originalPropertyValue =
                property.GetValue(originalObject,空)的字符串;
            串newPropertyValue =
                property.GetValue(changedObject,空)的字符串;

            如果(originalPropertyValue!= newPropertyValue)
            {
                list.Add(string.Concat(类名,property.Name,
                    改变从',originalPropertyValue,
                    '到',newPropertyValue,'));
            }
        }
    }

    返回列表;
}  

我在找System.IComparable,因为所有的数值类型(如的Int32和Double)实现IComparable,因为这样做字符串,字符和日期时间。这似乎是最好的方式找到任何财产,这不是一个自定义类。

攻到了年代由WCF或Web服务代理code产生听起来不错,但并没有给我要我的​​审计日志足够的信息(旧的和新的值)PropertyChanged事件。

寻找输入,以是否有更好的方式来做到这一点,非常感谢!

@Aaronaught,这里有一些例子code在生成基于这样的Object.Equals一个正匹配:

 地址地址1 =新的地址();
address1.StateProvince =新StateProvince();

地址地址2 =新的地址();
address2.StateProvince =新StateProvince();

IList的列表= Utility.GenerateAuditLogMessages(地址1,地址);  

  

[联系地址] StateProvince从改变   MyAccountService.StateProvince来   MyAccountService.StateProvince'

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

解决方案

IComparable的是排序比较。要么使用 IEquatable 来替代,或者只使用静态 System.Object.Equals 方法。后者有也工作,如果该对象不是一个原始类型,但仍通过覆盖定义了自己的相等比较等于

好处

 对象originalValue = property.GetValue(originalObject,NULL);
对象为newValue = property.GetValue(changedObject,NULL);
如果(!的Object.Equals(originalValue,为newValue))
{
    字符串originalText =(originalValue!= NULL)?
        originalValue.ToString():[空];
    字符串newText =(newText!= NULL)?
        newValue.ToString():[空];
    // 等等。
}
 

这显然是不完美的,但如果你只是做它,你控制类,那么您就可以确保它总是适用于您的特定需求。

有其他的方法来比较对象(如校验码,序列化等),但是这可能是最可靠的,如果类没有始终如一地贯彻 IPropertyChanged 和要真正知道它们的区别。


更新为新的例子code:

 地址地址1 =新的地址();
address1.StateProvince =新StateProvince();

地址地址2 =新的地址();
address2.StateProvince =新StateProvince();

IList的列表= Utility.GenerateAuditLogMessages(地址1,地址);
 

究其原因,使用的Object.Equals 在审计方法将导致在打是因为情况实际上是不相等的!

当然, StateProvince 可能为空在这两种情况下,但地址1 地址2 还有为 StateProvince 属性非空值,每个实例都不同。因此,地址1 地址2 有不同的属性。

让我们来翻转这个局面,借此code为例:

 地址地址1 =新的地址(35榆树街);
address1.StateProvince =新StateProvince(TX);

地址地址2 =新地址(35榆树街);
address2.StateProvince =新StateProvince(AZ);
 

如果这些被认为是平等的?那么,他们会用你的方法,因为 StateProvince 不执行 IComparable的。这就是为什么你的方法报道,这两个对象是在原来相同的情况下的唯一原因。因为 StateProvince 类没有实现 IComparable的,跟踪只是跳过该属性完全。但是,这两个地址都显然不等于!

这就是为什么我本来建议使用的Object.Equals ,因为这样你可以在 StateProvince 方法来覆盖它以获得更好的结果:

 公共类StateProvince
{
    公共字符串code {获得;组; }

    公众覆盖布尔等于(obj对象)
    {
        如果(OBJ == NULL)
            返回false;

        StateProvince SP = OBJ为StateProvince;
        如果(object.ReferenceEquals(SP,NULL))
            返回false;

        返回(SP code == code);
    }

    公共布尔等于(StateProvince SP)
    {
        如果(object.ReferenceEquals(SP,NULL))
            返回false;

        返回(SP code == code);
    }

    公众覆盖INT GetHash code()
    {
        返回code.GetHash code();
    }

    公共重写字符串的ToString()
    {
        返回的String.Format(code:[{0}],code);
    }
}
 

一旦你做到了这一点,在的Object.Equals code会很好地工作。而不是幼稚地检测是否地址1 地址2 字面上有相同的 StateProvince 引用,它实际上将检查语义平等的。


解决这个问题的另一种方法是延长追踪code实际上下降到子对象。换句话说,每个属性,检查 Type.IsClass 和可选的 Type.IsInterface 属性,如果,然后递归调用对物业本身的变化,跟踪方法,属性名返回prefixing任何审计结果递归。所以,你会最终有一个变化 StateProvince code

我用上面的方法,有时太多,但它更容易只覆盖等于上要比较语义平等(即审计)的对象,并提供适当的的ToString 覆盖,使得它清楚发生了什么变化。它没有规模的深嵌套,​​但我认为这是不寻常的要审核的方式。

最后一个技巧就是定义自己的接口,比如 IAuditable接口< T> ,其中采用相同类型作为参数的第二个实例,实际上返回一个列表(所有的差异或枚举)。它类似于我们覆盖的Object.Equals 方法同上,但还给更多信息。这是因为当对象图是非常复杂有用的,你知道你不能靠反射或等于。你可以用上面的办法结合起来;真的所有你需要做的就是替补 IComparable的 IAuditable接口并调用审核方法,如果实现该接口。

The project I'm working on needs some simple audit logging for when a user changes their email, billing address, etc. The objects we're working with are coming from different sources, one a WCF service, the other a web service.

I've implemented the following method using reflection to find changes to the properties on two different objects. This generates a list of the properties that have differences along with their old and new values.

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

I'm looking for System.IComparable because "All numeric types (such as Int32 and Double) implement IComparable, as do String, Char, and DateTime." This seemed the best way to find any property that's not a custom class.

Tapping into the PropertyChanged event that's generated by the WCF or web service proxy code sounded good but doesn't give me enough info for my audit logs (old and new values).

Looking for input as to if there is a better way to do this, thanks!

@Aaronaught, here is some example code that is generating a positive match based on doing 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 changed from 'MyAccountService.StateProvince' to 'MyAccountService.StateProvince'"

It's two different instances of the StateProvince class, but the values of the properties are the same (all null in this case). We're not overriding the equals method.

解决方案

IComparable is for ordering comparisons. Either use IEquatable instead, or just use the static System.Object.Equals method. The latter has the benefit of also working if the object is not a primitive type but still defines its own equality comparison by overriding 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.
}

This obviously isn't perfect, but if you're only doing it with classes that you control, then you can make sure it always works for your particular needs.

There are other methods to compare objects (such as checksums, serialization, etc.) but this is probably the most reliable if the classes don't consistently implement IPropertyChanged and you want to actually know the differences.


Update for new example code:

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

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

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

The reason that using object.Equals in your audit method results in a "hit" is because the instances are actually not equal!

Sure, the StateProvince may be empty in both cases, but address1 and address2 still have non-null values for the StateProvince property and each instance is different. Therefore, address1 and address2 have different properties.

Let's flip this around, take this code as an example:

Address address1 = new Address("35 Elm St");
address1.StateProvince = new StateProvince("TX");

Address address2 = new Address("35 Elm St");
address2.StateProvince = new StateProvince("AZ");

Should these be considered equal? Well, they will be, using your method, because StateProvince does not implement IComparable. That's the only reason why your method reported that the two objects were the same in the original case. Since the StateProvince class does not implement IComparable, the tracker just skips that property entirely. But these two addresses are clearly not equal!

This is why I originally suggested using object.Equals, because then you can override it in the StateProvince method to get better results:

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

Once you've done this, the object.Equals code will work perfectly. Instead of naïvely checking whether or not address1 and address2 literally have the same StateProvince reference, it will actually check for semantic equality.


The other way around this is to extend the tracking code to actually descend into sub-objects. In other words, for each property, check the Type.IsClass and optionally the Type.IsInterface property, and if true, then recursively invoke the change-tracking method on the property itself, prefixing any audit results returned recursively with the property name. So you'd end up with a change for StateProvinceCode.

I use the above approach sometimes too, but it's easier to just override Equals on the objects for which you want to compare semantic equality (i.e. audit) and provide an appropriate ToString override that makes it clear what changed. It doesn't scale for deep nesting but I think it's unusual to want to audit that way.

The last trick is to define your own interface, say IAuditable<T>, which takes a second instance of the same type as a parameter and actually returns a list (or enumerable) of all of the differences. It's similar to our overridden object.Equals method above but gives back more information. This is useful for when the object graph is really complicated and you know you can't rely on Reflection or Equals. You can combine this with the above approach; really all you have to do is substitute IComparable for your IAuditable and invoke the Audit method if it implements that interface.

这篇关于发现两个C#对象之间的性质差异的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆