sharptooth
sharptooth

Reputation: 170489

How would I get all public properties of object faster than with PropertyInfo.GetValue()?

I have the following generic code that iterates over IEnumerable<T> (assuming all objects are exactly of type T) and for each item it retrieves all properties values and appends them to a 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
    }
}

and btw the IEnumerable comes from a non-trivial Linq-To-Sql query.

So I profile my code and find that of all my code 37,3% time is spent in System.Data.Linq.SqlClient.ObjectReaderCompiler.ObjectReader.MoveNext() and 31,59% time is spent in RuntimePropertyInfo.GetValue().

This is why I'd be happy to find another way of retrieving all properties - one that is faster than calling PropertyInfo.GetValue().

How could I achieve that (preferable keeping the code generic)?

Upvotes: 1

Views: 375

Answers (1)

Mike Zboray
Mike Zboray

Reputation: 40818

The strategy is to cache a delegate that is not attached to any instance of a particular object. This is a little trickier than it sounds.

Jon Skeet's article on the topic is good for the general case, however I thought I'd offer a simplified solution for this specific case.

The workhorse is Delegate.CreateDelegate which takes a MethodInfo and returns a delegate that takes the instance as a parameter. The MethodInfo is provided by PropertyInfo.GetGetMethod. The trick is that we want all the delegates to return an object but we can't simply use Func<T, object> as the delegate type. That type is not going to be compatible without a boxing instruction for value types.

To do that we leverage a helper method (below it is called CreateDelegateInternal) which we will invoke by reflection with the correct run-time types. Since we only call this once when loading the delegates, it is not too costly. Then we will wrap the strongly-typed delegate in a less strongly typed lambda which will insert the correct conversion.

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

and your code would look something like:

foreach (var item in list)
{
    foreach (var property in ReflectionHelper<T>.Properties)
    {
        object value = property(item);
        // feed to StringBuilder
    }
}

Obviously, you may want may add some validation (to check the property's CanRead property) and error handling. Also, you may want to tuple up the PropertyInfo in the output so you could print metadata like names, this is left simple for clarity.

Upvotes: 2

Related Questions