Лямбда-выражения <T, Func <TIN, TOUT >> и MethodInfo

При переносе проекта с VS2010 на VS2012 я столкнулся со следующей проблемой. Проект много использует Reflection, и для получения MethodInfo из интерфейса был размещен следующий код:

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;

Это прекрасно работало, скомпилировано с VS2010, но methodCallExpression.Arguments.Count() было 2, если код скомпилирован с VS2012 с целью.Net 4.0.

После декомпиляции я заметил, что компилятор генерирует другой код для одного и того же выражения.

Это проблема дизайна, потому что дизайн не должен ретранслировать "магические числа", такие как число 2 в methodCallExpression.Arguments[2]. Я попытался найти решение для этого, используя следующее:

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

Но externalmostExpression является нулевым.

Наконец, я заставил его работать, изменив выражение следующим образом:

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

Это не идеал, но он работает как на VS2010, так и на VS2012.

Есть ли способ найти MethodInfo из выражения, подобного следующему:

Expression<Func<ITest, ServiceResponse>> expression = scv => scv.Get(default(ServiceRequest));
MethodInfo methodInfo = GetInnerMethodInfo( expression );
Assert.AreEqual("Get", methodInfo.Name);

1 ответ

Решение

Я не уверен, почему есть разница в способе компиляции выражений. Но, если вы ищете информацию о методе в постоянном делегате, вы можете скомпилировать выражение с помощью ITest реализация, чтобы получить у делегатов MethodInfo, Например:

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;

..где Test какой-то класс и реализует ITest, Который работает в 2012 и 2010 годах.

Я не уверен, как вы можете получить информацию о методе из выражения в 2012 году без предварительной компиляции...

ОБНОВИТЬ:

Если компиляция выражения не является опцией, похоже, что компилятор генерирует другое выражение и помещает MethodInfo в MethodCallExpression.Object свойство в компиляторе C# 5. Вы можете проверить, не является ли это свойство null и использовать его значение для MethodInfoили продолжить получение элемента в Arguments коллекция. Например:

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;
}

Я использую запрос LINQ, чтобы получить элемент в Arguments Если вы предпочитаете жестко закодированный индекс, вы можете использовать его вместо этого. Также необходима более полная проверка ошибок.

Другие вопросы по тегам