In this blog we are going to dynamically create a delegate using reflection both paying attention to performance and correctness.
You can find the finished code here.
We will compare
- Hard-coded
- Reflection
- Dynamic
- Static Reflection with Constants
- Static Reflection with Closures
Background
For another project I'm working on I had to write something like this:
var action = val => obj.Prop = val;
observable.subscribe(action);
Where I don't know the type of the obj, the name of the property nor the type of val up front. So that means I have to create the action by using reflection. An extra constraint here is performance. This action will be called hundreds of times, so it cannot be slow.
Reference
Let's use this hard-coded piece of code as a reference. This one doesn't use reflection, so this is as good as it gets.
Stopwatch sw = new Stopwatch();
//test1, hard-coded
var d = new Dummy();
Action<string> action = val => d.Text = val;
sw.Start();
for (int i = 0; i < iterations; i++)
{
action.Invoke("value1");
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
Console.WriteLine(d.Text);
For the sake of this blog, I created a class called Dummy which has a Text property. 100 million iterations took about 1.5 seconds. So that's the time we want to approximate.
Naïve Reflection
First up, good old reflection without any optimizations:
var d = new Dummy();
object o = d;
var propName = "Text";
action = val => o.GetType().GetProperty(propName).SetValue(o, val);
This took about 54 seconds. Yikes! But there is a lot of room for improvement here.
Better Reflection
By simply getting the PropertyInfo up front I could improve the performance significantly.
var pi = o.GetType().GetProperty(propName);
action = val => pi.SetValue(o, val);
This took about 38 seconds. A lot better, but still not great.
The Lazy Man's Approach
Why are we even using Reflection? Let's use dynamic!
dynamic dy = o;
action = val => dy.Text = val;
This took about 5 seconds. I was amazed by the result. I have been using dynamics in the past because I was too lazy to use proper reflection and that turned out to be a good thing. I did cheat here, I hard-coded the Text property, so it's not a completely fair comparison. In the end, dynamic was not flexible enough for the goal in mind. So I had to abandon it.
Static Reflection
The cool thing about static reflection is that you can make a lambda expression at runtime and then compile it. Once it's compiled it's almost as fast as the hard-coded scenario.
I started by simply compiling the setter:
Action<object, string> setter = GetCompiledSetter(o, propName);
action = val => setter(o, val);
GetCompiledSetter basically builds the following lambda expression:
(object target, string value) => ((Dummy)target).Text = value
and looks like this:
1. private static Action<T, string> GetCompiledSetter<T>(T d, string propName)
2. {
3. var pi = d.GetType().GetProperty(propName);
4. ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
5. ParameterExpression valueExp = Expression.Parameter(typeof(string), "value");
6. Expression convertExpr = Expression.Convert(targetExp, d.GetType());
7. MemberExpression propExp = Expression.Property(convertExpr, pi);
8. BinaryExpression assignExp = Expression.Assign(propExp, valueExp);
9. var setter = Expression.Lambda<Action<T, string>>
(assignExp, targetExp, valueExp).Compile();
10. return setter;
11.}
Line 4 and 5 define the input parameters. line 6, 7 and 8 make up the body of the expression and step 9 compiles the whole thing. The type T will object, since it's called on an object and we don't know the exact type at the time. That's why the convert in step 6 is necessary.
That already looks a lot better. Unfortunately the performance doesn't seem all that great: 6.5 seconds. And I did assume the val was of type string here.
Compile the whole lambda
To improve performance even further, I decided to not just pre-compile the setter, but the whole lambda expression.
This time I'm trying to build and compile this lambda:
val => obj.Property = val
1. private static Action<TValue> GetCompiledLambda<TTarget, TValue>(TTarget d, string propName)
2. {
3. var pi = d.GetType().GetProperty(propName);
4. ParameterExpression valueExp = Expression.Parameter(typeof(TValue), "val");
5. MemberExpression propExp = Expression.Property(Expression.Constant(d), pi);
6. BinaryExpression assignExp = Expression.Assign(propExp, valueExp);
7. var lamb = Expression.Lambda<Action<TValue>>
(assignExp, valueExp).Compile();
8. return lamb;
9. }
Here TTarget is object and TValue is string. Step 4 produces the input, step 5 and 6 produce the body of the lambda. The performance boost is significant here: 1.5 seconds. It's almost identical to the hard-coded scenario!
But when it's too good to be true, it usually is. There is a deep dark secret in this lambda. Notice that I used Constant here. But is it really a constant? The value is captured from its surrounding context, so in fact, it's a closure.
In the project that I'm building, it actually doesn't matter because the object will not change after it has been fed to the lambda. But I wondered what I would have to do to get it right.
Final Attempt
I was struggling with the creation of this lambda with a closure. So after a while I decided to cheat and do some reverse-engineering.
Action<string> exp = val => d.Text = val;
The expression above is the hard-coded piece of code from the very beginning. My plan was to turn this into an expression tree and analyse it.
But then I saw this:
It turns out that there is no way to write this as an expression tree! After a night of crying the solution came to me. Maybe I can make a factory that will produce this lambda on the fly. That means I need something like this:
(TTarget target) => (TValue val) => target.Prop = val
And then I simply have to call it with my object to be captured. I ended up with this code:
1. private static Action<TValue> GetCompiledLambdaWithFactory<TTarget, TValue>(TTarget target, string propName)
2. {
3. var targetExp = Expression.Parameter(target.GetType(), "target"); //not using TTarget, want specific type
4. var valueExp = Expression.Parameter(typeof(TValue), "val");
5. var pi = target.GetType().GetProperty(propName);
6. MemberExpression propExp = Expression.Property(targetExp, pi);
7. var lamb = Expression.Lambda(
Expression.Lambda<Action<TValue>>(
Expression.Assign(propExp, valueExp),
valueExp),
targetExp);
8. var factory = lamb.Compile();
9. var inner = (Action<TValue>)factory.DynamicInvoke(target);
10. return inner;
11.}
Line 7 is where the factory lambda is created. On line 9, I call it with my target. And voila, there is compiled expression that I wanted. And with a time of 1.6 seconds I was more than pleased with the result.
This was the final score:
Method |
Time (ms) |
Hard-coded |
1512 |
Bad Reflection |
54062 |
Better Reflection |
38274 |
Dynamic |
5267 |
Compiled Setter |
7069 |
Compiled Lambda with Constant |
1461 |
Compiled Lambda with Closure |
1586 |
Once again, you can find the finished code here.