与PropertyInfo.GetValue()相比,如何更快地获得对象的所有公共属性?

时间:2014-04-18 08:06:53

标签: c# .net generics reflection

我有以下通用代码迭代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()更快的方法。

我怎样才能实现这一点(最好保持代码通用)?

1 个答案:

答案 0 :(得分:2)

策略是缓存未附加到特定对象的任何实例的委托。这听起来有点棘手。

关于这个主题的Jon Skeet的article对于一般情况是好的,但我认为我会为这个具体案例提供一个简化的解决方案。

主力是Delegate.CreateDelegate,它接受​​MethodInfo并返回一个以实例作为参数的委托。 MethodInfoPropertyInfo.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,以便您可以打印名称之类的元数据,为简单起见,这很简单。