Currently I’m working on a data synchronization tool in which I have finally had the need to use reflection. I’ll admit that I’m not an expert when it comes to reflection, but it isn’t that hard to work with either. And I have heard before about the performance hit you take with reflection, so up until now I hadn’t ever needed / wanted to use it, but I see now the power that reflection offers.
One day while perusing my RSS feeds for the day I was reading one of Scott Hanselman’s weekly source code posts in which the Ukadc.Diagnostics project uses Lightweight Code Generation (LCG) to speed up the retrieval of the value of a property using reflection. Pretty slick, but what I need is a faster way to SET the value of a property without knowing it’s type. Now, like I said before, I’m no reflection guru, nor am I an IL guru, but I thought I would give it a shot. Epic Fail.
I’m kind of thick headed though when it comes to a problem, I usually don’t give up unless I’m sure I can’t win, and I’m not giving up. So I decided go try doing this the C# 3.0 way, where I don’t have to IL generate anything. Ultimately, there is IL being generated, as reported by Reflector, but I’m not generating it, so all is well.
So first off, lets list our requirements.
- Improve performance of getting / setting properties via PropertyInfo
- Getter / Setter
- Support for known type and known return type
- Support for known type and unknown return type
- Support unknown type and unknown return type
For this post, I’m only going to cover FastProperty, but in the attachment I’ll include the files to support item #1 and item #2 when parts are unknown (FastProperty<T,P> and FastProperty<T>). The only real differences between each one is gradually replacing object for it’s generic counterpart.
So first, lets take a step back and talk about what a property actually is. If we want to set a property, we would just do something like
Employee e = new Employee();
e.ID = 4;
All is fine and well. But a property is just syntactic sugar over a method. In this instance we would have a set method called void set_ID(Integer) and a get method called Integer get_ID(). So what we really want to do is make a delegate for these methods. So to begin with, lets tackle the easiest method, the get method.
The Code
public class FastProperty
{
public PropertyInfo Property { get; set; }
public Func<object, object> GetDelegate;
public Action<object, object> SetDelegate;
public FastProperty(PropertyInfo property)
{
this.Property = property;
InitializeGet();
InitializeSet();
}
private void InitializeSet()
{
var instance = Expression.Parameter(typeof(object), "instance");
var value = Expression.Parameter(typeof(object), "value");
// value as T is slightly faster than (T)value, so if it's not a value type,
// use that
UnaryExpression instanceCast =
(!this.Property.DeclaringType.IsValueType)
? Expression.TypeAs(instance, this.Property.DeclaringType)
: Expression.Convert(instance, this.Property.DeclaringType);
UnaryExpression valueCast =
(!this.Property.PropertyType.IsValueType)
? Expression.TypeAs(value, this.Property.PropertyType)
: Expression.Convert(value, this.Property.PropertyType);
this.SetDelegate =
Expression
.Lambda<Action<object, object>>(
Expression.Call(instanceCast, this.Property.GetSetMethod(),
valueCast),
new ParameterExpression[] { instance, value })
.Compile();
}
private void InitializeGet()
{
var instance = Expression.Parameter(typeof(object), "instance");
UnaryExpression instanceCast =
(!this.Property.DeclaringType.IsValueType)
? Expression.TypeAs(instance, this.Property.DeclaringType)
: Expression.Convert(instance, this.Property.DeclaringType);
this.GetDelegate =
Expression
.Lambda<Func<object, object>>(
Expression.TypeAs(
Expression.Call(instanceCast, this.Property.GetGetMethod()),
typeof(object)),
instance)
.Compile();
}
public object Get(object instance)
{
return this.GetDelegate(instance);
}
public void Set(object instance, object value)
{
this.SetDelegate(instance, value);
}
}
Getter
The get method returns a value and takes no inputs, so we are going to use the Func<T,TResult> delegate because we want to return a value. Now, don’t freak out because that delegate takes in a parameter, but the method signature doesn’t, the input is the instance you are calling the property on. This is what we are basically going to do:
Func<object,object> getter = instance => return instance.Property;
or more correctly
Func<object,object> getter = instance => return instance.get_Property();
We are just going to represent this as an Expression tree. So lets get down to the nitty gritty. All of our wonderful expression stuff lives in the System.Linq.Expresssions namespace. To recreate our lambda expression we would do the following. If we look at the InitializeGet method, we start off by declaring a parameter called instance. This basically just saying that we’ll be passing in a parameter of type object into our lambda called instance. The next thing that we do is create an UnaryExpression that is casting our parameter from the type of object to the type of the property.
Why are we casting from object to the type you ask? Well, because we don’t know the type before compile time, we have to use a delegate of type Func<object,object>, so in order to create that delegate, we have to match that signature. But we also are going to be calling the get method on our instance variable, which isn’t of type object, so we are casting it.
So now, we are going to call the get_<Property> method. So we create an MethodCallExpression via the Expression.Call method and pass in the instance of our object, which is properly cast to the correct type, and the get method for the property, via PropertyInfo.GetGetMethod(). But guess what, we have a delegate that is expecting a return value of type object back, not what the get_<Property> is actually returning (unless it happens to be object), so we do another Expression.TypeAs to convert the value back to object.
Now finally we get the delegate for all of our hard work, so we call the Expression.Lambda<T> method and pass our expression, and any parameters needed for it, which we only have 1 of, which is our instance, and then finally call the Compile method on our LambdaExpression which will gives us our Func<object,object> delegate.
Simple as pie right? Well, if you kept up with all that, we should be able to figure out the setter.
Setter
The set method is a void and takes exactly 1 parameter, so we are going to use the Action<T1,T2> delegate. So what we are basically wanting to do is this:
Action<object, object> setter = (instance, value) => instance.Property = value;
or more correctly
Action<object, object> setter = (instance, value) => instance.set_Property(value);
So to begin with, we create parameters for instance, and for value, both of type object. Then what do we do next? Well, we cast them back to their proper type. Now we are going to call the method, just like we did with the get method, but we are going to pass in a parameter – value. Now all we need to do is create our Lambda and compile. And we are done.
Performance
For this performance test, I’m using the Get/Set implementation put forth by Pete the CodeSlinger which uses IL Generation and DynamicMethod, the Expression Tree method which I have put forth, the “classic” reflection approach, and, of course, the native approach.
For 10,000,000 iterations, here is the time in milliseconds for each approach. The results are kind of creepy in the way the it’s almost a mirror on each side of the decimal point.
- Native
- Get – 169.0169
- Set – 1874.1874
- PropertyCaller
- Get – 389.0389
- Set – 1966.1966
- FastProperty
- Get – 301.0301
- Set – 1956.1956
- FastProperty
- Get – 470.047
- Set – 2247.2247
- FastProperty
- Get – 516.0516
- Set – 2277.2277
- PropertyInfo
- Get – 7313.7313
- Set – 14163.4162
- MethodInfo – via PropertyInfo.GetGetMethod / PropertyInfo.GetSetMethod
- Get – 6960.696
- Set – 13702.3701
As we can see, using Expression Trees we can get MUCH better performance than using the standard reflection methods even when we don’t know type or return type. And the only performance penalty we pay is for boxing. Also, each FastProperty call up there assumes calling the Set / Get method on the FastProperty instance. If we call the actual delegate (GetDelegate and SetDelegate) we actually achieve better performance than the PropertyCaller delegates in the FastProperty<T,P> instance, and the difference between then FastProperty<T> and FastProperty instances is 3 milliseconds on the get, and 19 milliseconds on the set.
The more you know before hand, the better.
So, although I don’t think it needs to be stated, I will, just to be thorough. The more you know before hand, the better this will perform. So, if you don’t know the type you will be using, or the return type, use FastProperty. If you know the type, but not the return type of the property, use FastProperty<T> because then you can avoid the casting. It’s not a terrible performance hit, but developers are perfectionists, lets not kid ourselves. And if you are just going to want to get a property of a type, for whatever reason (you should be going native, but I don’t know your situation) then use FastProperty<T,P>.
Where to go next?
Well, I’m just laying out the framework that I have built, I’ll leave it up to you to do whatever you want. It’s faster to invoke the delegates than to call the Get / Set method on the FastProperty class, but I leave that there to allow you to add error handling (i.e. you can see if the value is assignable from the property.PropertyType, and throw an exception there instead of it coming up from the delegate code, which will be hard to track down).
Source
You can pick up all the source for this over at the FastReflection project at code.msdn.com. I’ll probably be moving this over to codeplex eventually and supported more things via reflection (fields, methods?), we’ll see. For now, i’m just going to put everything up at code.msdn.com.
License
This code is released under the “OMG LOL go pwn the developer next to you with this new knowledge” license. Ok not really, I’m just putting Ms-PL for code.msdn.com, which I believe lets you do anything you want. If not, let me know and I’ll change the license. Hopefully this has been helpful for all who stumble upon this.
Happy .NETing,
Darren