Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda expressions <T, Func<TIN,TOUT>> and MethodInfo

While migrating a project from VS2010 to VS2012, I ran into the following problem. The project is using Reflection a lot, and in order to get the MethodInfo from an interface the following code was placed:

Expression<Func<ITest, Func<ServiceRequest, ServiceResponse>>> expression = scv => scv.Get;

UnaryExpression unaryExpression = expression.Body as UnaryExpression;

MethodCallExpression methodCallExpression = unaryExpression.Operand as MethodCallExpression;

ConstantExpression constantExpression = methodCallExpression.Arguments[2] as ConstantExpression;

MethodInfo myMethod = constantExpression.Value as MethodInfo;

This worked fine compiled with VS2010, but the methodCallExpression.Arguments.Count() was 2 if the code is compiled with VS2012 aiming .Net 4.0.

After decompiling, I noticed that the compiler was generating different code for the same expression.

This is a design problem because the design should not relay on "magic numbers" like the number 2 on methodCallExpression.Arguments[2]. I tried to find a solution for this using the following:

MethodCallExpression outermostExpression = expression .Body as MethodCallExpression;
MethodInfo myMethod = outermostExpression.Method;

But outermostExpression is null.

Finally, I made it work changing the expression as follow:

Expression<Func<ITest, ServiceResponse>> expression = scv => scv.Get(default(ServiceRequest));
MethodCallExpression outermostExpression = expression.Body as MethodCallExpression;
Assert.AreEqual("Get", outermostExpression.Method.Name);

It is not ideal, but it works on both VS2010 and VS2012.

Is there a way to find the MethodInfo from an expression like the following:

Expression<Func<ITest, ServiceResponse>> expression = scv => scv.Get(default(ServiceRequest));
MethodInfo methodInfo = GetInnerMethodInfo( expression );
Assert.AreEqual("Get", methodInfo.Name);
like image 494
Darien Avatar asked Aug 28 '12 20:08

Darien


1 Answers

I'm not sure why there's a difference in the way the expressions compile. But, if you're looking for the method info of the method in the constant delegate, you can compile the expression with an ITest implementation to get at the delegates MethodInfo. For example:

Expression<Func<ITest, Func<ServiceRequest, ServiceResponse>>> expression = scv => new Func<ServiceRequest, ServiceResponse>(scv.Get);
Func<ServiceRequest, ServiceResponse> ret = expression.Compile()(new Test());
MethodInfo methodInfo = ret.Method;

..where Test is some class and implements ITest. Which works in both 2012 and 2010.

I'm unsure how you can get that method info from the expression in 2012 without first compiling it...

UPDATE:

If compiling the expression isn't an option, it appears that the compiler is generating a different expression and putting the MethodInfo in the MethodCallExpression.Object property in the C# 5 compiler. You can check to see if that property is not null and use its value for the MethodInfo, or continue on getting an element in the Arguments collection. For example:

Expression<Func<ITest, Func<ServiceRequest, ServiceResponse>>> expression = 
    scv => new Func<ServiceRequest, ServiceResponse>(scv.Get);

UnaryExpression unaryExpression = expression.Body as UnaryExpression;

MethodCallExpression methodCallExpression = 
    unaryExpression.Operand as MethodCallExpression;

ConstantExpression constantExpression = 
    methodCallExpression.Object as ConstantExpression;

MethodInfo methodInfo;
if (constantExpression != null)
{
    methodInfo = constantExpression.Value as MethodInfo;
}
else
{
    constantExpression = methodCallExpression.Arguments
                            .Single(a => a.Type == typeof(MethodInfo) 
                                && a.NodeType == ExpressionType.Constant) as
                            ConstantExpression;
    methodInfo = constantExpression.Value as MethodInfo;
}

I'm using the LINQ query to get at the element in the Arguments collection, if you prefer the hard-coded index, you could probably use that instead. More complete error-checking is necessary as well.

like image 102
Peter Ritchie Avatar answered Nov 15 '22 06:11

Peter Ritchie