比较两个对象(不包括一些属性)的最快方法
本文关键字:属性 方法 不包括 两个 对象 比较 | 更新日期: 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;
}
}