用lambda表达式替换参数。

11

考虑以下代码:

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相同的表达式。请注意,我不想对每个foos中的Foo计算exp0,而是要获得两个新表达式。

[更新]:
基本上,我想能够将传递给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)"

你能给我们举个例子,说明如何使用它以及输出结果是什么吗? - Jon Senchyna
请参见https://dev59.com/mlbTa4cB1Zd3GeqP7yB4。 - AakashM
1个回答

21

我会采用以下方式:

编写一个参数替换表达式访问器,按照以下方式操作原始表达式:

  1. 从lambda签名中完全删除您不想要的参数。
  2. 将所有对参数的使用替换为所需的索引器表达式。

这是我根据我的另一个问题上的早期回答编写的一个快速而简单的示例:

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);

1
对于嵌套的lambda表达式,我发现必须使用Expression.Lambda(...)而不是Expression.Lambda<TOutput>(...)。无论如何,类型参数版本似乎都是不必要的。 - HappyNomad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接