代码之家  ›  专栏  ›  技术社区  ›  Thomas Flinkow

获取表达式方法调用目标的快速方法

  •  1
  • Thomas Flinkow  · 技术社区  · 7 年前

    给定以下代码行代码,

    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 呼叫有没有办法拿到那张票 目标

    基准代码:

    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 回复  |  直到 7 年前
        1
  •  6
  •   Ivan Stoev    7 年前

    我能想到的唯一办法就是避免时间的浪费 Compile

    一般来说(处理所有案件)这样做是一项复杂的任务。目前有80多家 ExpressionType s、 它们都具有不同的语义(有些属于具有相应基类的类别)。为了处理所有这些问题,可能需要创建自定义 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代码发出,不计算 DynamicMethod 和委托来调用它。

        2
  •  1
  •   Chris DaMour    5 年前

    随着时间的推移,我对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();
                }
    
            }