我有以下通用代码迭代IEnumerable<T>
(假设所有对象都是T
类型),并且对于每个项目,它检索所有属性值并将它们附加到StringBuilder
:
Type t = typeof(T);
var properties = t.GetProperties();
foreach (var item in list)
{
foreach (var property in properties)
{
object value = property.GetValue(item, null);
// feed to StringBuilder
}
}
并且btw IEnumerable
来自一个非平凡的Linq-To-Sql查询。
因此,我对我的代码进行了分析,并发现我所有代码中的代码在System.Data.Linq.SqlClient.ObjectReaderCompiler.ObjectReader.MoveNext()
中花费了37,3%的时间,在RuntimePropertyInfo.GetValue()
花费了31,59%的时间。
这就是为什么我很乐意找到另一种检索所有属性的方法 - 一种比调用PropertyInfo.GetValue()
更快的方法。
我怎样才能实现这一点(最好保持代码通用)?
答案 0 :(得分:2)
策略是缓存未附加到特定对象的任何实例的委托。这听起来有点棘手。
关于这个主题的Jon Skeet的article对于一般情况是好的,但我认为我会为这个具体案例提供一个简化的解决方案。主力是Delegate.CreateDelegate
,它接受MethodInfo
并返回一个以实例作为参数的委托。 MethodInfo
由PropertyInfo.GetGetMethod
提供。诀窍是我们希望所有代理都返回object
,但我们不能简单地使用Func<T, object>
作为委托类型。没有值类型的装箱指令,该类型将不兼容。
为此,我们利用一个辅助方法(在它下面称为CreateDelegateInternal
),我们将通过反射使用正确的运行时类型来调用它。由于我们在加载代表时只调用一次,因此代价不会太高。然后我们将强类型委托包装在一个不太强类型的lambda中,它将插入正确的转换。
public static class ReflectionHelper<T>
{
private static readonly IEnumerable<Func<T, object>> propertyDelegates = CreateDelegates().ToArray();
private static IEnumerable<Func<T, object>> CreateDelegates()
{
var helper = typeof(ReflectionHelper<T>).GetMethod("CreateDelegateInternal", BindingFlags.Static | BindingFlags.NonPublic);
var properties = typeof(T).GetProperties();
foreach(var property in properties)
{
var method = helper.MakeGenericMethod(typeof(T), property.PropertyType);
yield return (Func<T, object>)method.Invoke(null, new object[] { property.GetGetMethod() });
}
}
private static Func<T, object> CreateDelegateInternal<T, TReturn>(MethodInfo m)
{
var f = (Func<T, TReturn>)Delegate.CreateDelegate(typeof(Func<T, TReturn>), m);
return t => (object)f(t);
}
public static IEnumerable<Func<T, object>> Properties
{
get { return propertyDelegates; }
}
}
您的代码看起来像:
foreach (var item in list)
{
foreach (var property in ReflectionHelper<T>.Properties)
{
object value = property(item);
// feed to StringBuilder
}
}
显然,您可能希望添加一些验证(检查属性的CanRead
属性)和错误处理。此外,您可能希望在输出中元组化PropertyInfo
,以便您可以打印名称之类的元数据,为简单起见,这很简单。