将参数替换为指向lambda表达式中的嵌套参数

时间:2016-07-21 08:43:18

标签: c# lambda expression visitor

由于previous question上的一些答案,我可以成功替换lambda表达式中的简单参数类型,但我无法弄清楚如何将传入的lambda中的参数替换为嵌套参数。

考虑以下对象:

public class ColourRepository {

    private IList<DtoColour> Colours { get; set; } 

    public ColourRepository()
    {
        var favColours = new Collection<DtoFavouriteColour>
        {
            new DtoFavouriteColour() { Person = new DtoPerson("Peter", "Parker") },
            new DtoFavouriteColour() { Person = new DtoPerson("John", "Smith") },
            new DtoFavouriteColour() { Person = new DtoPerson("Joe", "Blogs") }
        };
        Colours = new List<DtoColour>
        {
            new DtoColour("Red") { FavouriteColours = favColours },
            new DtoColour("Blue"),
            new DtoColour("Yellow")
        };
    }

    public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
    {
        var coonvertedPred = MyExpressionVisitor.Convert(predicate);
        return Colours.Where(coonvertedPred).Select(c => new DomainColour(c.Name)).ToList();
    }
}

和存储库:

public class MyExpressionVisitor : ExpressionVisitor
{
    private ReadOnlyCollection<ParameterExpression> _parameters;

    public static Func<DtoColour, bool> Convert<T>(Expression<T> root)
    {
        var visitor = new MyExpressionVisitor();
        var expression = (Expression<Func<DtoColour, bool>>)visitor.Visit(root);
        return expression.Compile();
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        var param = _parameters?.FirstOrDefault(p => p.Name == node.Name);

        if (param != null)
        {
            return param;
        }

        if(node.Type == typeof(DomainColour))
        {
            return Expression.Parameter(typeof(DtoColour), node.Name);
        }

        if (node.Type == typeof(DomainPerson))
        {
            return Expression.Parameter(typeof(DtoFavouriteColour), node.Name);
        }

        return node;
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        _parameters = VisitAndConvert<ParameterExpression>(node.Parameters, "VisitLambda");
        return Expression.Lambda(Visit(node.Body), _parameters);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        var exp = Visit(node.Expression);

        if (node.Member.DeclaringType == typeof(DomainColour))
        {
            if (node.Type == typeof(ICollection<DomainPerson>))
            {
                return Expression.MakeMemberAccess(exp, typeof(DtoColour).GetProperty("FavouriteColours"));
            }

            return Expression.MakeMemberAccess(exp, typeof(DtoColour).GetProperty(node.Member.Name));
        }

        if (node.Member.DeclaringType == typeof(DomainPerson))
        {
            var nested = Expression.MakeMemberAccess(exp, typeof(DtoFavouriteColour).GetProperty("Person"));
            return Expression.MakeMemberAccess(nested, typeof(DtoPerson).GetProperty(node.Member.Name));
        }

        return base.VisitMember(node);
    }
}

最后是一个表达式访问者,它应该将谓词转换为Dto模型

的正确谓词
1[ExpressionVisitorTests.DtoFavouriteColour]'
  cannot be used for parameter of type
  'System.Collections.Generic.IEnumerable

目前我收到以下例外

  

[System.ArgumentException:类型的表达式   &#39; {了System.Collections.Generic.ICollection {1}} 1 [ExpressionVisitorTests.DomainPerson]&#39;   方法&#39;布尔   任何[DomainPerson](System.Collections.Generic.IEnumerable 1[ExpressionVisitorTests.DomainPerson], System.Func 2 [ExpressionVisitorTests.DomainPerson,System.Boolean])&#39;]

以下是dotnetfiddle无效。

提前感谢您的帮助。

2 个答案:

答案 0 :(得分:6)

经过一些搜索后,我this answer John Skeet找到了dotnetfiddle,这导致我想出了一个有效的解决方案,其中包含ExpressionVisitor方法的覆盖MethodInfo使用新的protected override Expression VisitMethodCall(MethodCallExpression node) { if (node.Method.DeclaringType == typeof(Enumerable) && node.Arguments[0].Type == typeof(ICollection<DomainPerson>)) { Expression obj = Visit(node.Object); IEnumerable<Expression> args = Visit(node.Arguments); if (obj != node.Object || args != node.Arguments) { var generic = typeof(Enumerable).GetMethods() .Where(m => m.Name == node.Method.Name) .Where(m => m.GetParameters().Length == node.Arguments.Count) .Single(); var constructed = generic.MakeGenericMethod(typeof(DtoFavouriteColour)); return Expression.Call(obj, constructed, args); } } return node; } 替换原始的_parameters以获得正确的集合类型。

VisitLambda<T>

我还需要确保我对node.Body集合的引用并未被访问protected override Expression VisitLambda<T>(Expression<T> node) { var parameters = VisitAndConvert(node.Parameters, "VisitLambda"); // ensure parameters set but dont let original reference // be overidden by nested calls _parameters = parameters; return Expression.Lambda(Visit(node.Body), parameters); } 时可能发生的{{1}}嵌套调用所取代。

{{1}}

请参阅{{3}}了解完全有效的解决方案。

如果有人有更好/更优雅的解决方案,请为我添加一个答案标记。

答案 1 :(得分:4)

你已经解决了具体的问题,所以我不能说我要提出的是更好/更优雅,但肯定是更通用(删除了具体的类型/属性/假设),因此可以重复用于翻译来自不同模型类型的类似表达式。

以下是代码:

public class ExpressionMap
{
    private Dictionary<Type, Type> typeMap = new Dictionary<Type, Type>();
    private Dictionary<MemberInfo, Expression> memberMap = new Dictionary<MemberInfo, Expression>();
    public ExpressionMap Add<TFrom, TTo>()
    {
        typeMap.Add(typeof(TFrom), typeof(TTo));
        return this;
    }
    public ExpressionMap Add<TFrom, TFromMember, TTo, TToMember>(Expression<Func<TFrom, TFromMember>> from, Expression<Func<TTo, TToMember>> to)
    {
        memberMap.Add(((MemberExpression)from.Body).Member, to.Body);
        return this;
    }
    public Expression Map(Expression source) => new MapVisitor { map = this }.Visit(source);

    private class MapVisitor : ExpressionVisitor
    {
        public ExpressionMap map;
        private Dictionary<Type, ParameterExpression> parameterMap = new Dictionary<Type, ParameterExpression>();
        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            return Expression.Lambda(Visit(node.Body), node.Parameters.Select(Map));
        }
        protected override Expression VisitParameter(ParameterExpression node) => Map(node);
        protected override Expression VisitMember(MemberExpression node)
        {
            var expression = Visit(node.Expression);
            if (expression == node.Expression)
                return node;
            Expression mappedMember;
            if (map.memberMap.TryGetValue(node.Member, out mappedMember))
                return Visit(mappedMember);
            return Expression.PropertyOrField(expression, node.Member.Name);
        }
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (node.Object == null && node.Method.IsGenericMethod)
            {
                // Static generic method
                var arguments = Visit(node.Arguments);
                var genericArguments = node.Method.GetGenericArguments().Select(Map).ToArray();
                var method = node.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArguments);
                return Expression.Call(method, arguments);
            }
            return base.VisitMethodCall(node);
        }
        private Type Map(Type type)
        {
            Type mappedType;
            return map.typeMap.TryGetValue(type, out mappedType) ? mappedType : type;
        }
        private ParameterExpression Map(ParameterExpression parameter)
        {
            var mappedType = Map(parameter.Type);
            ParameterExpression mappedParameter;
            if (!parameterMap.TryGetValue(mappedType, out mappedParameter))
                parameterMap.Add(mappedType, mappedParameter = Expression.Parameter(mappedType, parameter.Name));
            return mappedParameter;
        }
    }
}

以及具体示例的用法:

public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
{
    var map = new ExpressionMap()
        .Add<DomainColour, DtoColour>()
        .Add((DomainColour c) => c.People, (DtoColour c) => c.FavouriteColours.Select(fc => fc.Person))
        .Add<DomainPerson, DtoPerson>();
    var mappedPredicate = ((Expression<Func<DtoColour, bool>>)map.Map(predicate));
    return Colours.Where(mappedPredicate.Compile()).Select(c => new DomainColour(c.Name)).ToList();
}

正如您所看到的,它允许您使用“fluent”语法定义从一种类型到另一种类型的简单映射,并且可选地从一种类型的成员定义到另一种类型的成员/表达式(一旦它们兼容) lambda表达式。没有指定映射的成员按名称映射,如原始代码中所示。

一旦定义了映射,当然实际处理由自定义ExpressionVisitor完成,与您的类似。区别在于它通过类型映射和合并ParameterExpression,并且还翻译每个静态通用方法,因此也应该与Queryable一起使用类似。