【问题标题】:How do I merge (or dynamically build and combine) lambda expressions in C#?如何在 C# 中合并(或动态构建和组合)lambda 表达式?
【发布时间】:2014-10-13 15:07:15
【问题描述】:

我正在尝试动态组合 lambda 表达式。下面的代码将解释我想要什么。这不是将 a=>b 和 b=>c 组合为 a=>c 的情况。相反,我想通过重用转换表达式来防止代码重复:

class User // convert from...
{
  public string FirstName {get;set;}
  public string LastName {get;set;}
}

class Person // convert to...
{
  public string Name
}

public class UserTransaction
{
  User FromUser {get;set;}
  User ToUser {get;set;}
  decimal Amount {get;set;}
}

public class PersonTransaction
{
  Person FromPerson {get;set;}
  Person ToPerson {get;set;}
  bool IsPositive;
}

Expression<Func<User, Person>> ToPerson = u => new Person {Name = u.FirstName + " " + u.LastName};
Expression<Func<UserTransaction, PersonTransaction>> PersonTransaction = ut => new PersonTransaction {
  FromPerson = FromUser.Compile()(ut.FromUser), // Actually, I do not want to compile
  ToPerson = ToUser.Compile()(ut.FromUser), // (or double code)
  IsPositive = ut.Amount > 0
}

在上面的示例中,我已经有一个表达式可以将用户转换为人。我不想复制或编译此代码。我尝试通过手动编辑表达式树来去除“编译”调用。我没有成功。有没有人尝试过类似的方法并成功了?

【问题讨论】:

  • 您应该研究像 AutoMapper 这样的工具,它是为进行此类转换而构建的。
  • @Andrew 我在我的应用程序的完全不同的部分使用 AutoMapper。它用于自动将对象的内容复制到另一个对象。它如何转换表达式? (一些上下文:表达式用于最终生成 sql 查询。)
  • 它不处理表达式,但从您的代码看来,您正在尝试将User 转换为PersonUserTransactionPersonTransaction-- 两种类型之间的映射
  • 嗯,我需要一个从 UserTransaction 映射到 PersonTransaction 的 表达式。这个表达式被输入到现有代码中。

标签: c# lambda merge expression


【解决方案1】:

你可以用ExpressionVisitor 做一些巫术来重写你现有的代码完全内联;这个:

  • 检测到调用
  • 定位编译
  • 解析原始 lambda
  • 直接交换所有参数值
  • 相应地重建表达式树

玩得开心!

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;

public class User // convert from...
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Person // convert to...
{
    public string Name { get; set; }
}

public class UserTransaction
{
    public User FromUser { get; set; }
    public User ToUser { get; set; }
    public decimal Amount { get; set; }
}

public class PersonTransaction
{
    public Person FromPerson { get; set; }
    public Person ToPerson { get; set; }
    public bool IsPositive { get; set; }
}

static class Program
{

    static void Main()
    {
        Expression<Func<User, Person>> ToPerson = u => new Person { Name = u.FirstName + " " + u.LastName };
        Expression<Func<UserTransaction, PersonTransaction>> PersonTransaction = ut => new PersonTransaction
        {
            FromPerson = ToPerson.Compile()(ut.FromUser), // Actually, I do not want to compile
            ToPerson = ToPerson.Compile()(ut.ToUser), // (or double code)
            IsPositive = ut.Amount > 0
        };
        var visitor = new RemoveCompilationsExpressionVisitor();


        var inlined = (Expression<Func<UserTransaction, PersonTransaction>>)visitor.Visit(PersonTransaction);
    }

    class ParameterSwapExpressionVisitor :ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, Expression> swaps;

        public ParameterSwapExpressionVisitor(Dictionary<ParameterExpression, Expression> swaps)
        {
            this.swaps = swaps;
        }
        protected override Expression VisitParameter(ParameterExpression node)
        {
            Expression result;
            return swaps.TryGetValue(node, out result) ? result : base.VisitParameter(node);
        }
    }
    class RemoveCompilationsExpressionVisitor : ExpressionVisitor
    {
        protected override Expression VisitInvocation(InvocationExpression node)
        {
            var lambda = TryGetInnerLambda(node.Expression);
            if(lambda != null)
            {
                // this would be a partial solution, but we want to go further!
                // return Expression.Invoke(lambda, node.Arguments);

                var swaps = new Dictionary<ParameterExpression, Expression>();
                for(int i = 0; i < lambda.Parameters.Count; i++)
                {
                    swaps.Add(lambda.Parameters[i], node.Arguments[i]);
                }
                var visitor = new ParameterSwapExpressionVisitor(swaps);
                return visitor.Visit(lambda.Body);
            }
            return base.VisitInvocation(node);
        }

        LambdaExpression TryGetInnerLambda(Expression node)
        {
            try
            {
                if(node.NodeType == ExpressionType.Call)
                {
                    var mce = (MethodCallExpression)node;
                    var method = mce.Method;
                    if (method.Name == "Compile" && method.DeclaringType.IsGenericType && method.DeclaringType.GetGenericTypeDefinition()
                        == typeof(Expression<>))
                    {
                        object target;
                        if (TryGetLiteral(mce.Object, out target))
                        {
                            return (LambdaExpression)target;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                /* best effort only */
                Debug.WriteLine(ex);
            }
            return null;
        }

        static bool TryGetLiteral(Expression node, out object value)
        {
            value = null;
            if (node == null) return false;
            switch(node.NodeType)
            {
                case ExpressionType.Constant:
                    value = ((ConstantExpression)node).Value;
                    return true;
                case ExpressionType.MemberAccess:
                    var me = (MemberExpression)node;
                    object target;
                    if (TryGetLiteral(me.Expression, out target))
                    {
                        switch (me.Member.MemberType)
                        {
                            case System.Reflection.MemberTypes.Field:
                                value = ((FieldInfo)me.Member).GetValue(target);
                                return true;
                            case MemberTypes.Property:
                                value = ((PropertyInfo)me.Member).GetValue(target, null);
                                return true;
                        }
                    }
                    break;

            }
            return false;
        }
    }

}

【讨论】:

  • 这就是我的想法。我尝试一次性完成,但使用两个表达式访问者让一切变得更容易!
  • @reallbert 确实;请注意,对于递归编译/调用链,参数交换访问者可以只继承调用检测。
  • 好的,谢谢!我现在无法从包含表达式属性的静态转换类中获取表达式( class X { static Expression> Y {get{return p => new Q {};}} ),但我我想我最终会成功。
猜你喜欢
  • 2018-08-07
  • 2015-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-15
相关资源
最近更新 更多