J. *_*mes 8 c# expression-trees asp.net-mvc-3
我正在寻找一种方法来组合两个lambda表达式,而不使用Expression.Invoke任何一个表达式.我想基本上构建一个链接两个独立的表达式的新表达式.请考虑以下代码:
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; }
}
Run Code Online (Sandbox Code Playgroud)
可以说我有两个表达方式:
Expression<Func<Model, Foo>> expression1 = m => m.SubModel.Foo;
Expression<Func<Foo, string>> expression2 = f => f.Bar.Value;
Run Code Online (Sandbox Code Playgroud)
我想将它们连接在一起以在功能上获得以下表达式:
Expression<Func<Model, string>> joinedExpression = m => m.SubModel.Foo.Bar.Value;
Run Code Online (Sandbox Code Playgroud)
我能想到的唯一方法是使用这样的ExpressionVisitor:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
然后用它像这样:
var expExt = new ExpressionExtender<Model, Foo>(expression1);
var joinedExpression = expExt.Extend(expression2);
Run Code Online (Sandbox Code Playgroud)
它有效,但对我来说感觉有点笨拙.我仍然试图包裹我的头脑表达,并想知道是否有一种更惯用的方式来表达这一点,我有一种偷偷摸摸的怀疑,我错过了一些明显的东西.
我想这样做的原因是将它与ASP.net mvc 3 Html助手一起使用.我有一些深度嵌套的ViewModel和一些HtmlHelper扩展来帮助处理这些,所以表达式只需要MemberExpressions内置MVC帮助器的集合来正确处理它们并构建正确深度嵌套的名称属性值.我的第一直觉是使用Expression.Invoke()并调用第一个表达式并将其链接到第二个表达式,但MVC帮助程序并不那么喜欢.它失去了它的等级背景.
Mar*_*ell 21
使用访问者将参数的所有实例交换f到m.SubModel.Foo,并使用参数创建一个新表达式m:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
您的解决方案似乎根据您的具体问题进行了狭窄的调整,这似乎不灵活.
在我看来,你可以通过简单的lambda替换直接解决你的问题:用body替换参数的实例(或者在lambda演算中称为"自由变量").(请参阅Marc对某些代码的回答.)
由于表达式树中的参数具有引用标识而不是值标识,因此甚至不需要对它们进行alpha重命名.
也就是说,你有:
Expression<Func<A, B>> ab = a => f(a); // could be *any* expression using a
Expression<Func<B, C>> bc = b => g(b); // could be *any* expression using b
Run Code Online (Sandbox Code Playgroud)
你想生产这种成分
Expression<Func<A, C>> ac = a => g(f(a)); // replace all b with f(a).
Run Code Online (Sandbox Code Playgroud)
所以拿走身体g(b),做一个搜索和替换的访客寻找ParameterExpressionfor b,并用身体替换它f(a)给你新的身体g(f(a)).然后使用a具有该主体的参数创建一个新的lambda .