问题描述
我正在寻找一种方法来组合两个 lambda 表达式,而无需在任一表达式上使用 Expression.Invoke
.我想基本上构建一个链接两个独立表达式的新表达式.考虑以下代码:
I am looking for a way to combine two lambda expressions, without using an Expression.Invoke
on either expression. I want to essentially build a new expression that chains two separate ones. Consider the following code:
class Model {
public SubModel SubModel { get; set;}
}
class SubModel {
public Foo Foo { get; set; }
}
class Foo {
public Bar Bar { get; set; }
}
class Bar {
public string Value { get; set; }
}
假设我有两种表达方式:
And lets say I had two expressions:
Expression<Func<Model, Foo>> expression1 = m => m.SubModel.Foo;
Expression<Func<Foo, string>> expression2 = f => f.Bar.Value;
并且我想将它们连接在一起以在功能上获得以下表达式:
And I want to join them together to functionally get the following expression:
Expression<Func<Model, string>> joinedExpression = m => m.SubModel.Foo.Bar.Value;
我能想到的唯一方法是使用这样的 ExpressionVisitor:
The only way I could think to do this is to use a ExpressionVisitor like this:
public class ExpressionExtender<TModel, TIntermediate> : ExpressionVisitor
{
private readonly Expression<Func<TModel, TIntermediate>> _baseExpression;
public ExpressionExtender(Expression<Func<TModel, TIntermediate>> baseExpression)
{
_baseExpression = baseExpression;
}
protected override Expression VisitMember(MemberExpression node)
{
_memberNodes.Push(node.Member.Name);
return base.VisitMember(node);
}
private Stack<string> _memberNodes;
public Expression<Func<TModel, T>> Extend<T>(Expression<Func<TIntermediate, T>> extend)
{
_memberNodes = new Stack<string>();
base.Visit(extend);
var propertyExpression = _memberNodes.Aggregate(_baseExpression.Body, Expression.Property);
return Expression.Lambda<Func<TModel, T>>(propertyExpression, _baseExpression.Parameters);
}
}
然后它是这样使用的:
var expExt = new ExpressionExtender<Model, Foo>(expression1);
var joinedExpression = expExt.Extend(expression2);
它有效,但对我来说感觉有点笨重.我仍然在试着把我的头表情包起来,想知道是否有更惯用的方式来表达这一点,我偷偷地怀疑我错过了一些明显的东西.
It works, but it feels a bit clunky to me. I'm still trying to wrap my head expressions and wondering if there is a more idiomatic way to express this, and I have the sneaky suspicion that I missing something obvious.
原因我想这样做是为了将它与 ASP.net mvc 3 Html 帮助程序一起使用.我有一些深层嵌套的 ViewModel 和一些 HtmlHelper 扩展可以帮助处理这些问题,因此表达式需要只是 MemberExpressions
的集合,以便内置的 MVC 帮助程序正确处理它们并构建正确的深层嵌套名称属性值.我的第一直觉是使用 Expression.Invoke()
并调用第一个表达式并将其链接到第二个表达式,但是 MVC 助手不太喜欢那样.它失去了层次结构.
The reason I want to do this is to use it with the ASP.net mvc 3 Html helpers. I have some deeply nested ViewModels and some HtmlHelper extensions that help deal with those, so the expression needs to be just a collection of MemberExpressions
for the built in MVC helpers to process them correctly and build the correctly deeply nested name attribute values. My first instinct was to use Expression.Invoke()
and invoke the first expression and chain it to the second, but the MVC helpers didn't like that very much. It lost its hierarchical context.
推荐答案
使用访问者将参数 f
的所有实例交换为 m.SubModel.Foo
,以及创建一个以 m
为参数的新表达式:
Use a visitor to swap all instances of the parameter f
to m.SubModel.Foo
, and create a new expression with m
as the parameter:
internal static class Program
{
static void Main()
{
Expression<Func<Model, Foo>> expression1 = m => m.SubModel.Foo;
Expression<Func<Foo, string>> expression2 = f => f.Bar.Value;
var swap = new SwapVisitor(expression2.Parameters[0], expression1.Body);
var lambda = Expression.Lambda<Func<Model, string>>(
swap.Visit(expression2.Body), expression1.Parameters);
// test it worked
var func = lambda.Compile();
Model test = new Model {SubModel = new SubModel {Foo = new Foo {
Bar = new Bar { Value = "abc"}}}};
Console.WriteLine(func(test)); // "abc"
}
}
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);
}
}
这篇关于组合 Lambda 表达式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!