将2个表达式与First()组合在一起

时间:2015-02-25 17:43:01

标签: c# asp.net lambda expression expression-trees

我有2个表达式:

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

现在我需要将它们组合成一个看起来像这样的表达式:

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

这需要与ASP.Net MVC DisplayFor(...)

一起使用

我尝试使用Invoke组合它们,但这会在调用DisplayFor时导致异常。

2 个答案:

答案 0 :(得分:2)

您可以使用表达式访问者将lambda组合在一起,而无需任何反射。使用Compose扩展方法(您可以在下面查看),您可以将多个表达式链接到一个表达式中:

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

您可以这样测试:

// 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"

以下是我正在使用的与您的TModelTListTListValue对应的类:

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

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

以下是用于实现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);
    }
}

信用到期日:

答案 1 :(得分: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
    );