比较2个对象除了几个属性的最快方法?

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

这是我到目前为止使用reflection的代码:

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

这项工作一直很顺利,但现在我让用户在一次上传中比较了50,000多个项目,这需要很长时间。

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

如果您只使用reflection来创建和使用上述逻辑编译方法,那肯定会更快。 这应该比反映每个对象快得多。

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

下面是我在框架中使用的一段代码,用于读取对象的所有属性以跟踪状态更改。 在这种情况下,我不知道该对象的任何属性名称。 所有属性值都放在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 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指出的那样,动态发出代码并缓存结果会更快,这意味着你只需要reflection命中一次,而不是每个对象一次。

另请注意,根据.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; } }