替换lambda表达式中的参数

时间:2012-06-22 15:48:54

标签: c# lambda expression-trees partial-application

考虑此代码:

public class Foo
{
    public int a { get; set; }
    public int b { get; set; }
}

private void Test()
{
    List<Foo> foos = new List<Foo>();
    foos.Add(new Foo());
    foos.Add(new Foo());
    Expression<Func<Foo, int>> exp0 = f => f.a * f.b;
    Expression<Func<int>> exp1 = () => foos[0].a * foos[0].b;
    Expression<Func<int>> exp2 = () => foos[1].a * foos[1].b;
}

如何将exp0转换为与exp1exp2相同的两个表达式。请注意,我不想仅为exp0中的每个Foo评估foos,而是获得两个新表达式。

[更新]

基本上,我希望能够将传递给Linq扩展方法(例如Sum的表达式)扩展或“展平”到枚举中每个项目的一个表达式中,因为这些枚举将是静态的,因为我已经有代码读取不带参数的表达式(然后将它们转换成另一种语言)。

我使用MetadataToken作为对具有特定属性的属性的引用(在这种情况下ab将具有此属性)并将其与字典一起使用将C#属性与另一种语言的变量相关联:

Foo foo = new Foo();
Expression<Func<int>> exp = () => foo.a * foo.a + foo.b;
string result1 = GetResult(exp); // gets "v_001 * v_001 + v_002"

List<Foo> foes = new List<Foo>();
foes.Add(new Foo());
foes.Add(new Foo());
Expression<Func<int>> exp2 = () => foes.Sum(f => f.a * f.a + f.b);
string result2 = GetResult(exp2); // should get "(v_001 * v_001 + v_002) + (v_003 * v_003 + v_004)"

1 个答案:

答案 0 :(得分:19)

我会这样做:

编写一个参数 - replacer表达式 - 访问者,操作原始表达式如下:

  1. 从lambda签名中删除完全不需要的参数。
  2. 用所需的索引器表达式替换参数的所有用法。
  3. 这是一个快速而肮脏的样本我根据我的earlier answer在另一个问题上掀起了:

    public static class ParameterReplacer
    {
        // Produces an expression identical to 'expression'
        // except with 'source' parameter replaced with 'target' expression.     
        public static Expression<TOutput> Replace<TInput, TOutput>
                        (Expression<TInput> expression,
                        ParameterExpression source,
                        Expression target)
        {
            return new ParameterReplacerVisitor<TOutput>(source, target)
                        .VisitAndConvert(expression);
        }
    
        private class ParameterReplacerVisitor<TOutput> : ExpressionVisitor
        {
            private ParameterExpression _source;
            private Expression _target;
    
            public ParameterReplacerVisitor
                    (ParameterExpression source, Expression target)
            {
                _source = source;
                _target = target;
            }
    
            internal Expression<TOutput> VisitAndConvert<T>(Expression<T> root)
            {
                return (Expression<TOutput>)VisitLambda(root);
            }
    
            protected override Expression VisitLambda<T>(Expression<T> node)
            {
                // Leave all parameters alone except the one we want to replace.
                var parameters = node.Parameters
                                     .Where(p => p != _source);
    
                return Expression.Lambda<TOutput>(Visit(node.Body), parameters);
            }
    
            protected override Expression VisitParameter(ParameterExpression node)
            {
                // Replace the source with the target, visit other params as usual.
                return node == _source ? _target : base.VisitParameter(node);
            }
        }
    }
    

    您的方案的用法(已测试):

    var zeroIndexIndexer = Expression.MakeIndex
            (Expression.Constant(foos),
             typeof(List<Foo>).GetProperty("Item"), 
             new[] { Expression.Constant(0) });
    
    
    // .ToString() of the below looks like the following: 
    //  () =>    (value(System.Collections.Generic.List`1[App.Foo]).Item[0].a
    //         *  value(System.Collections.Generic.List`1[App.Foo]).Item[0].b)
    var exp1Clone = ParameterReplacer.Replace<Func<Foo, int>, Func<int>>
                      (exp0, exp0.Parameters.Single(), zeroIndexIndexer);