Частичная оценка выражений - PullRequest
0 голосов
/ 30 января 2020

Я хочу записать вызов метода как выражение, но предварительно оценить его аргументы безопасным для типов и дружественным к среде IDE способом.

Взять следующий пример класса:

class Test
{
    public string SomeMethod(int a, string b) { ... }
}

Теперь я хочу передать вызов SomeMethod функции, получающей Expression:

HandleMethodCall<Test>(test => test.SomeMethod("string".Length, 5.ToString()));

Прямо сейчас HandleMethodCall имеет подпись

static void HandleMethodCall<T>(Expression<Func<T, object>> expr)

К сожалению, в в этом случае выражение, переданное в HandleMethodCall, содержит не только test.SomeMethod(), но и деревья выражений для аргументов.

Обходным решением будет разделить вызов метода и его аргументы на разные аргументы для HandleMethodCall, но это приводит к потере безопасности во время компиляции и поддержки IDE для отображения имен аргументов.

Есть ли способ заставить компилятор оценивать части выражения перед тем, как поместить их в Expression object?

Или, альтернативно, можно ли вручную вызвать оценку деревьев выражений для параметров метода?

1 Ответ

1 голос
/ 31 января 2020

Похоже, что это невозможно определить во время компиляции, но можно скомпилировать и оценить части выражения во время выполнения, используя Expression.Lambda<>(...).Compile()() (см. документация ).

Это приводит к следующей реализации (без простоты проверки ошибок):

public static void HandleMethodCall<T>(Expression<Func<T, object>> expr)
{
    // Extract method call
    var methodCall = (MethodCallExpression)expr.Body;

    // Run through expected arguments and evaluate the supplied ones
    var expectedArguments = methodCall.Method.GetParameters();
    for(int i = 0; i < expectedArguments.Length; ++i)
    {
        Console.Write(expectedArguments[i].Name + ": ");

        // Since we do not know the argument type in advance, we have to use Func<object>
        var argumentEvaluationExpression = Expression.Lambda<Func<object>>(Expression.Convert(methodCall.Arguments[i], typeof(object)));
        object argumentValue = argumentEvaluationExpression.Compile()();

        Console.WriteLine(argumentValue);
    }
}

Вызов этой функции с помощью

HandleMethodCall<Test>(test => test.SomeMethod("string".Length, "1" + " 2 " + (1 + 2).ToString()));

приводит к ожидаемому результату

a: 6
b: 1 2 3

Однако я не уверен, будет ли это действительно работать при любых условиях. Кроме того, я ожидаю, что это будет довольно медленно из-за шага Compile().

Спасибо @RyanWilson за полезные комментарии, которые позволили мне найти это решение!

...