Быстрый способ получить цель вызова метода Expression - PullRequest
0 голосов
/ 09 января 2019

Учитывая следующую строку кода,

Expression<Action> expression = () => target.ToString();

существует ли быстрый способ получения объекта target?

Ниже работает код

public object GetExpressionTarget<T>(Expression<T> expression)
{
    MethodCallExpression methodCall = (MethodCallExpression) expression.Body;
    LambdaExpression theTarget = Expression.Lambda(methodCall.Object, null);
    Delegate compiled = theTarget.Compile();

    return compiled.DynamicInvoke();    }

но очень, очень медленно.


Есть ли более быстрый способ получить цель выражения вызова метода?


Сравнительный анализ моего кода (GetDelegate, DelegateCompile и DelegateDynamicInvoke), а также кода @ IvanStoev (GetFunc, FuncCompile и FuncInvoke) дает следующий результат:

|                Method |           Mean |         Error |        StdDev |
|---------------------- |----------------|---------------|---------------|
|       DelegateCompile | 165,068.477 ns | 2,671.3001 ns | 2,498.7358 ns |
|           FuncCompile | 160,956.199 ns | 2,133.5343 ns | 1,995.7093 ns |
| DelegateDynamicInvoke |   1,148.191 ns |    11.7213 ns |    10.9642 ns |
|            FuncInvoke |       3.040 ns |     0.0264 ns |     0.0247 ns |

Итак, Invoke на самом деле намного быстрее, чем DynamicInvoke, но узким местом является вызов Compile. Есть ли способ получить объект target без компиляции выражений?

Код теста:

public class Program
{
    private Delegate @delegate;
    private Func<object> func;

    private static Delegate GetDelegate(Expression<Action> expression)
    {
        MethodCallExpression methodCall = (MethodCallExpression) expression.Body;
        return Expression.Lambda(methodCall.Object, null).Compile();
    }

    private static Func<object> GetFunc(Expression<Action> expression)
    {
        MethodCallExpression methodCall = (MethodCallExpression) expression.Body;
        return Expression.Lambda<Func<object>>(methodCall.Object).Compile();
    }

    [GlobalSetup]
    public void Setup()
    {
        object o = new object();
        Expression<Action> expression = () => o.ToString();

        this.@delegate = Program.GetDelegate(expression);
        this.func = Program.GetFunc(expression);
    }

    [Benchmark]
    public void DelegateCompile()
    {
        object o = new object();
        Expression<Action> expression = () => o.ToString();

        Program.GetDelegate(expression);
    }

    [Benchmark]
    public void FuncCompile()
    {
        object o = new object();
        Expression<Action> expression = () => o.ToString();

        Program.GetFunc(expression);
    }

    [Benchmark]
    public void DelegateDynamicInvoke()
    {
        this.@delegate.DynamicInvoke();
    }

    [Benchmark]
    public void FuncInvoke()
    {
        this.func.Invoke();
    }

    public static void Main(string[] args)
    {
        BenchmarkRunner.Run<Program>();
    }
}

1 Ответ

0 голосов
/ 12 января 2019

Единственный способ избежать операции, требующей затрат времени Compile, - это рекурсивная оценка содержимого выражения с помощью отражения.

Выполнение этого в общем (обработка всех случаев) - сложная задача. В настоящее время существует более 80 ExpressionType с, все они с различной семантикой (ну, некоторые попадают в категории с соответствующими базовыми классами). Чтобы справиться с ними всеми, вероятно, следует создать пользовательский ExpressionVisitor и внедрить механизм оценки (возможно, с каким-то стеком оценки).

Другими словами, много работы / кода.

Однако ... Если мы ограничим выражения двумя типами - ConstantExpression (постоянное значение) и MemberExpression (поле или свойство постоянного значения), то существует относительно простое решение. Рассматриваемый метод уже содержит предположение о переданном Expression<Action>, и цель примера выражения (которая является замыканием) попадает в категорию полей с постоянными значениями.

Основная работа выполняется в частном рекурсивном методе следующим образом:

static object Evaluate(Expression expression)
{
    if (expression == null)
        return null;
    if (expression is ConstantExpression constExpression)
        return constExpression.Value;
    if (expression is MemberExpression memberExpression)
    {
        var target = Evaluate(memberExpression.Expression);
        if (memberExpression.Member is FieldInfo field)
            return field.GetValue(target);
        if (memberExpression.Member is PropertyInfo property)
            return property.GetValue(target);
    }
    throw new NotSupportedException();
}

и рассматриваемый метод с его использованием будет

public object GetExpressionTarget<T>(Expression<T> expression)
{
    var methodCall = (MethodCallExpression)expression.Body;
    return Evaluate(methodCall.Object);
}

У меня нет результатов сравнения производительности, но даже при использовании отражения, он должен быть намного быстрее, чем Compile, использующий отражение и динамического кода IL, не считая создания DynamicMethod и делегат для вызова.

...