Our UI system can generate a form from a MethodInfo. Before System.Linq.Expressions, we were getting the MethodInfo using reflection (method 1):
MethodInfo info = typeof(ExtensionTestClass).GetMethod("InstanceMethod", BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(string) }, null);
The bad part about this is that if we changed the signature or name of InstanceMethod, the code would still compile.
Enter expressions. Now we do this (method 2):
MethodInfo info = GetMethod<ExtensionTestClass>(x => x.InstanceMethod("defaultValue", "defaultValue"));
or this (method 3):
MethodInfo info = GetMethod<ExtensionTestClass, string, string>(x => x.InstanceMethod);
The syntax is "better", we get intellisense, and we get compilation errors if the method doesn't exist or the signature doesn't match. However, method 2 and method 3 are about 10 to 20 times slower than reflection.
Some numbers (measured with StopWatch):
Single Call: Method 1: .0000565 Method 2: .0004272 Method 3: .0019222
100000 Calls: Method 1: .1171071 Method 2: 1.5648544 Method 3: 2.0602607
We don't actually compile the expression or execute it, and I'm interested if anyone has an explanation for the difference in performance.
UPDATE: The GetMethod<> code:
Method 2:
public static MethodInfo GetMethod<T>(Expression<Action<T>> target)
{
MethodCallExpression exp = target.Body as MethodCallExpression;
if (exp != null)
{
return exp.Method;
}
return null;
}
Method 3:
public static MethodInfo GetMethod<T, A1, A2>(Expression<Func<T, Action<A1, A2>>> expression)
{
var lambdaExpression = (LambdaExpression)expression;
var unaryExpression = (UnaryExpression)lambdaExpression.Body;
var methodCallExpression = (MethodCallExpression)unaryExpression.Operand;
var methodInfoExpression = (ConstantExpression)methodCallExpression.Arguments.Last();
return (MethodInfo)methodInfoExpression.Value;
}
My guess is that it's slower because the expression versions do the same reflection (although they might use the IL shortcut methodof
which has no analogue in C#) in order to create the expression trees, in addition to the overhead of creating the trees themselves for every call (I don't think they are cached by the code that the compiler emits); plus you then have to read those trees to get the method back out.
Reflection might be 'slow' but in actual fact it's pretty darned fast; especially since I believe the data, behind the scenes, is cached as well. Thus once you've called GetMethod
the second call will be faster. That provides another compelling proof for why the subsequent expression tree versions are slower - since they are actually doing more work.
If you're comfortable with IL, compile a version with all three and then analyse the compiled image with ILSpy or Reflector (in C# mode both will be clever and re-consitute the expression code back into C#, which is no good; so switch to IL mode) - take a look at the code that's emitted to produce the expression trees and you'll see what I mean.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With