比较两个对象(不包括一些属性)的最快方法

本文关键字:属性 方法 不包括 两个 对象 比较 | 更新日期: 2023-09-27 18:27:08

我有一个网站,用户可以在其中上传数据,我只想更新属性已更改的数据。因此,我正在比较两个相同类型的对象以进行更改,并且我需要排除一些属性,例如ModifiedOn,它是一个日期。

以下是我迄今为止使用反射的代码:

 private bool hasChanges(object OldObject, object newObject)
        {
            var oldprops = (from p in OldObject.GetType().GetProperties() select p).ToList();
            var newprops = (from p in newObject.GetType().GetProperties() select p).ToList();
            bool isChanged = false;
            foreach (PropertyInfo i in oldprops)
            {
                if (checkColumnNames(i.Name))
                {
                    var newInfo = (from x in newprops where x.Name == i.Name select x).Single();
                    var oldVal = i.GetValue(OldObject, null);
                    var newVal = newInfo.GetValue(newObject, null);
                    if (newVal == null || oldVal == null)
                    {
                        if (newVal == null && oldVal != null)
                        {
                            isChanged = true;
                            return true;
                        }
                        if (oldVal == null && newVal != null)
                        {
                            isChanged = true;
                              return true;
                        }
                    }
                    else
                    {
                        if (!newVal.Equals(oldVal))
                        {
                            isChanged = true;
                         return true;
                        }
                    }
                }
            }
            return isChanged;
        }

我用这种方法忽略某些列:

private bool checkColumnNames(string colName)
        {
            if (
                colName.ToLower() == "productid" ||
                colName.ToLower() == "customerid" ||
                colName.ToLower() == "shiptoid" ||
                colName.ToLower() == "parentchildid" ||
                colName.ToLower() == "categoryitemid" ||
                 colName.ToLower() == "volumepricingid" ||
                colName.ToLower() == "tagid" ||
                colName.ToLower() == "specialprice" ||
                colName.ToLower() == "productsmodifierid" ||
                colName.ToLower() == "modifierlistitemid" ||
                colName.ToLower() == "modifierlistid" ||
                colName.ToLower() == "categoryitemid" ||
                colName.ToLower() == "createdon" ||
                colName.ToLower() == "createdby" ||
                colName.ToLower() == "modifiedon" ||
                colName.ToLower() == "modifiedby" ||
                colName.ToLower() == "deletedon" ||
                colName.ToLower() == "deletedby" ||
                colName.ToLower() == "appendproductmodifiers" ||
                colName.ToLower() == "introdate" ||
                colName.ToLower() == "id" ||
                colName.ToLower() == "discontinued" ||
                colName.ToLower() == "stagingcategories"
                )
                return false;
            return true;
        }

这一直运行得很好,只是现在我有用户在一次上传中比较50000多个项目,这需要很长时间。

有没有更快的方法来实现这一点?

比较两个对象(不包括一些属性)的最快方法

使用表达式树或动态方法编译和缓存代码。您可能会看到10-100倍的性能提升。您的原始反射代码来检索属性,您可以将其用作创建编译版本的基础。

示例

以下是我在一个框架中使用的代码片段,用于读取对象的所有属性以跟踪状态更改。在这个场景中,我不知道对象的任何属性名。所有特性值都放置在StringBuilder中。

我已经从我的原始代码中简化了这一点;它仍然可以编译,但您可能需要对其进行调整。

private static DynamicMethod CreateChangeTrackingReaderIL( Type type, Type[] types )
{
    var method = new DynamicMethod( string.Empty, typeof( string ), new[] { type } );
    ILGenerator il = method.GetILGenerator();
    LocalBuilder lbInstance = il.DeclareLocal( type );
    // place the input parameter of the function onto the evaluation stack
    il.Emit( OpCodes.Ldarg_0 ); 
    // store the input value
    il.Emit( OpCodes.Stloc, lbInstance ); 
    // declare a StringBuilder
    il.Emit( OpCodes.Newobj, typeof( StringBuilder ).GetConstructor( Type.EmptyTypes ) ); 
    foreach( Type t in types )
    {
        // any logic to retrieve properties can go here...
        List<PropertyInfo> properties = __Properties.GetTrackableProperties( t );
        foreach( PropertyInfo pi in properties )
        {
            MethodInfo mi = pi.GetGetMethod();
            if( null == mi )
            {
                continue;
            }
            il.Emit( OpCodes.Ldloc, lbInstance ); // bring the stored reference onto the eval stack
            il.Emit( OpCodes.Callvirt, mi ); // call the appropriate getter method
            if( pi.PropertyType.IsValueType )
            {
                il.Emit( OpCodes.Box, pi.PropertyType ); // box the return value if necessary
            }
            // append it to the StringBuilder
            il.Emit( OpCodes.Callvirt, typeof( StringBuilder ).GetMethod( "Append", new Type[] { typeof( object ) } ) ); 
        }
    }
    // call ToString() on the StringBuilder
    il.Emit( OpCodes.Callvirt, typeof( StringBuilder ).GetMethod( "ToString", Type.EmptyTypes ) ); 
    // return the last value on the eval stack (output of ToString())
    il.Emit( OpCodes.Ret ); 
    return method;
}

请注意,如果您不熟悉IL生成,大多数人会发现表达式树更容易使用。任何一种方法都有类似的结果。

如果您只是使用反射来创建和编译使用上述逻辑的方法,那么速度肯定会更快。这应该比在每个物体上反射要快得多。

是否保证对象具有相同的类型?即使不是,你也可以检查它们,如果它们相同的类型,则将它们发送到以下方法:

private bool hasChanges(object OldObject, object newObject) 
    { 
        var props = OldObject.GetType().GetProperties();
        foreach (PropertyInfo i in props) 
        { 
            if (checkColumnNames(i.Name)) 
            { 
                var oldVal = i.GetValue(OldObject, null); 
                var newVal = i.GetValue(newObject, null); 
                if (newVal == null) 
                {
                    if (oldVal != null) 
                    { 
                        return true; 
                    }
                }
                else if (oldVal == null)
                {
                    return true;
                }
                else if (!newVal.Equals(oldVal)) 
                { 
                    return true; 
                } 
            } 
        } 
        return false; 
    } 

这只比您的方法稍微有效一些。正如Tim Medora和PinnyM所指出的,动态地发出代码并缓存结果会更快,这意味着你只接受一次反射命中,而不是每个对象一次。

还要注意的是,根据.NET Framework中使用字符串的最佳实践,您应该使用ToUpper而不是ToLower进行字符串比较,但您应该使用String.Equals(string, string, StringComparison),而不是自己转换大小写。这至少有一个优点:如果字符串的长度不同,Equals将返回false,因此您可以跳过大小写转换。这也会节省一点时间。

另一个想法:

如果对象是不同类型的,您仍然可以通过加入属性集合来改进算法,而不是使用重复的线性搜索:

private bool hasChanges(object OldObject, object newObject)  
    {  
        var oldprops = OldObject.GetType().GetProperties();  
        var newprops = newObject.GetType().GetProperties();
        var joinedProps = from oldProp in oldprops
                          join newProp in newProps
                              on oldProp.Name equals newProp.Name
                          select new { oldProp, newProp }
        foreach (var pair in joinedProps)  
        {  
            if (checkColumnNames(pair.oldProp.Name))  
            {  
                var oldVal = pair.oldProp.GetValue(OldObject, null);  
                var newVal = pair.newProp.GetValue(newObject, null);  
//etcetera

最后的想法(灵感来自Tim Schmelter的评论):

在类上覆盖object.Equals,并使用

private bool HasChanges(object o1, object o2) { return !o1.Equals(o2); }

样品类别:

class SomeClass
{
    public string SomeString { get; set; }
    public int SomeInt { get; set; }
    public DateTime SomeDateTime { get; set; }
    public bool Equals(object other)
    {
        SomeClass other1 = other as SomeClass;
        if (other1 != null)
            return other1.SomeInt.Equals(SomeInt)
                && other1.SomeDateTime.Equals(SomeDateTime)
                && other1.SomeString.Equals(SomeString); //or whatever string equality check you prefer
        //possibly check for other types here, if necessary
        return false;
    }
}