Reputation: 170489
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
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