0

I have 2 Expressions:

Expression<Func<TModel, IEnumerable<TList>>> list = model => list;
Expression<Func<TList, TListValue>> valueProperty = listEntry => listEntry.property;

Now I need to combine them into one expression that looks like this:

model => model.list.First().property

And this needs to work with ASP.Net MVC DisplayFor(...)

I tried combining them using Invoke, but this will result in an Exception when DisplayFor is called.

svick
  • 236,525
  • 50
  • 385
  • 514
wertzui
  • 5,148
  • 3
  • 31
  • 51

2 Answers2

2

You can compose your lambdas together using an expression visitor, without any reflection necessary. Using a Compose extension method (that you can check out below) you can chain together multiple expressions into a single expression:

// Your original lambdas
Expression<Func<A, IEnumerable<B>>> listGetter = a => a.List;
Expression<Func<B, string>> propGetter = b => b.SomeString;

// Stitch them together
var firstSomeStringGetter = listGetter.Compose(seq => seq.First()).Compose(propGetter);

You can test this out like so:

// Test out what the body of the expression is
Console.WriteLine(firstSomeStringGetter.Body.ToString()); // Outputs "a.List.First().SomeString"

// Test out invoking the expression
var result = firstSomeStringGetter.Compile()(new A());
Console.WriteLine(result); // Outputs "Foo"

Here's the classes I'm using that correspond to your TModel, TList and TListValue:

class A
{
    public IEnumerable<B> List
    {
        get
        {
            yield return new B();
        }
    }
}

class B
{
    public string SomeString
    {
        get { return "Foo"; }
    }
}

Here's the helper classes used to implement Compose:

// https://stackoverflow.com/users/1117815/felipe
static class FunctionalExtensions
{
    public static Expression<Func<A, C>> Compose<A, B, C>(this Expression<Func<A, B>> f, Expression<Func<B, C>> g)
    {
        var ex = SubstituteIn(g.Body, g.Parameters[0], f.Body);

        return Expression.Lambda<Func<A, C>>(ex, f.Parameters[0]);
    }
    static TExpr SubstituteIn<TExpr>(TExpr expression, Expression orig, Expression replacement) where TExpr : Expression
    {
        var replacer = new SwapVisitor(orig, replacement);
        return replacer.VisitAndConvert(expression, "SubstituteIn");
    }
}

// https://stackoverflow.com/users/23354/marc-gravell
class SwapVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public SwapVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

Credit where it's due:

Community
  • 1
  • 1
Asad Saeeduddin
  • 46,193
  • 6
  • 90
  • 139
1
Expression<Func<TModel, IEnumerable<TList>>> exp1 = model => model.list;
Expression<Func<TList, TListValue>> exp2 = listEntry => listEntry.property;

// get method info of "IEnumerable<>.First()", i.e. "Enumerable.First(this IEnumerable<>)"
MethodInfo first =
    typeof (System.Linq.Enumerable).GetMethods()
        .Where(m => m.Name == "First" && m.GetParameters().Count() == 1)
        .Single()
        .MakeGenericMethod(new Type[]{typeof(TList)});

var body = Expression.Property(
    Expression.Call(null, first, exp1.Body), // model.list.First()
    (exp2.Body as MemberExpression).Member as PropertyInfo  // get property info of "TList.property"
    );
Expression<Func<TModel, TListValue>> result = Expression.Lambda<Func<TModel, TListValue>>(
    body,
    exp1.Parameters
    );
Sam
  • 133
  • 10