13

Considering this code:

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

How can you take exp0 and turn it into two expressions identical to exp1 and exp2. Note that I don't want to just evaluate exp0 for each Foo in foos, but instead get two new expressions.

[Update]:

Basically, I want to be able to expand or "flatten" an expression passed to a Linq extension method such as Sum into one expression per item in the enumeration since these enumerations will be static, and because I already have code that reads expressions that don't take parameters (and then turns them into another language).

I'm using the MetadataToken as a references to properties that have a certain attribute (in this case a and b would have this attribute) and using it with a dictionary that correlates C# properties to another language's variables:

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)"
2
  • Can you give us an example of how you would use this and what the output would be? Commented Jun 22, 2012 at 15:57
  • See also stackoverflow.com/questions/5631070/… Commented Jun 22, 2012 at 16:07

1 Answer 1

24

I would do it this way:

Write a parameter-replacer expression-visitor that manipulates the original expression as follows:

  1. Gets rid of the parameter you don't want entirely from the lambda signature.
  2. Replaces all uses of the parameter with the desired indexer expression.

Here's a quick and dirty sample I whipped up based on my earlier answer on a different question:

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

Usage for your scenario (tested):

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);
Sign up to request clarification or add additional context in comments.

1 Comment

For nested lambdas, I found I must use Expression.Lambda(...) instead of Expression.Lambda<TOutput>(...). The type parameter version seems unnecessary in any case.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.