获取Expression方法调用目标的快速方法

时间:2019-01-09 14:10:20

标签: c# performance delegates expression dynamic-invoke

给出以下代码行,

!apt-get update && apt-get install caffe-cpu -y --allow-unauthenticated

是否有一种 fast 方法来获取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(); } GetDelegateDelegateCompile)以及@IvanStoev的代码(DelegateDynamicInvokeGetFuncFuncCompile)进行基准测试产生以下结果:

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

2 个答案:

答案 0 :(得分:4)

唯一可以避免耗时的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);
}

我没有性能比较的结果,但是即使它使用反射,它也应该比使用反射动态IL代码发出的Compile快得多,这还不算创建DynamicMethod并委托它进行调用。

答案 1 :(得分:1)

随着时间的推移,我对 Ivan 的解决方案进行了扩展,以防它对其他人有所帮助

        static object Evaluate(
            Expression expression
        )
        {
            switch (expression)
            {
                case ConstantExpression e:
                    return e.Value;

                case MemberExpression e when e.Member is FieldInfo field:
                    return field.GetValue(
                        Evaluate(
                            e.Expression
                        )
                    );

                case MemberExpression e when e.Member is PropertyInfo property:
                    return property.GetValue(
                        Evaluate(
                            e.Expression
                        )
                    );

                case ListInitExpression e when e.NewExpression.Arguments.Count() == 0:
                    var collection = e.NewExpression.Constructor.Invoke(new object[0]);
                    foreach(var i in e.Initializers)
                    {
                        i.AddMethod.Invoke(
                            collection,
                            i.Arguments
                                .Select(
                                    a => Evaluate(a)
                                )
                                .ToArray()
                        );
                    }
                    return collection;

                case MethodCallExpression e:
                    return e.Method.Invoke(
                        Evaluate(e.Object),
                        e.Arguments
                            .Select(
                                a => Evaluate(a)
                            )
                            .ToArray()
                    );

                default:
                    //TODO: better messaging
                    throw new NotSupportedException();
            }

        }