代码之家  ›  专栏  ›  技术社区  ›  Stargazer

转换表达式树类型

  •  1
  • Stargazer  · 技术社区  · 9 年前

    我一直在寻找解决问题的办法。

    我找到了几个简单的表达方式,比如

    var exp1 Expression<Func<T, bool>> x => x.Name == "MyName"
    

    但我遇到了这样的问题:

    var exp1 Expression<Func<T, bool>> x => x.Category.Name == "Coupe"
    

    对于简单的表达式,我可以将任何表达式从一种类型(T)转换为另一种(TT),在其他情况下,我也需要这样做,更复杂。。。

    有谁能帮忙指点一下?以下是我目前所获得的:

    private class CustomVisitor<T> : ExpressionVisitor
    {
    private readonly ParameterExpression mParameter;
    
    public CustomVisitor(ParameterExpression parameter)
    {
        mParameter = parameter;
    }
    
    //this method replaces original parameter with given in constructor
    protected override Expression VisitParameter(ParameterExpression node)
    {
        return mParameter;
    }
    private int counter = 0;
    
    /// <summary>
    /// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression" />.
    /// </summary>
    /// <param name="node">The expression to visit.</param>
    /// <returns>
    /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
    /// </returns>
    /// <exception cref="System.NotImplementedException"></exception>
    protected override Expression VisitMember(MemberExpression node)
    {
        counter++;
        System.Diagnostics.Debug.WriteLine("{0} - {1}", node.ToString(), counter);
        try
        {
            //only properties are allowed if you use fields then you need to extend
            // this method to handle them
            if (node.Member.MemberType != System.Reflection.MemberTypes.Property)
                throw new NotImplementedException();
    
            //name of a member referenced in original expression in your 
            //sample Id in mine Prop
            var memberName = node.Member.Name;
            //find property on type T (=PersonData) by name
            var otherMember = typeof(T).GetProperty(memberName);
            //visit left side of this expression p.Id this would be p
            var inner = Visit(node.Expression);
    
            return Expression.Property(inner, otherMember);
        }
        catch (Exception ex)
        {
            return null;
        }
    }
    }
    

    实用方法:

    public static Expression<Func<TDestin, T>> ConvertTypesInExpression<TSource, TDestin, T>(Expression<Func<TSource, T>> source)
    {
        var param = Expression.Parameter(typeof(TDestin));
    
        var body = new CustomVisitor<TDestin>(param).Visit(source.Body);
    
        Expression<Func<TDestin, T>> lambda = Expression.Lambda<Func<TDestin, T>>(body, param);
    
        return lambda;
    }
    

    它是这样使用的:

    var changedFilter = ConvertTypesInExpression<ClientNotificationRuleDto, ClientNotificationRule, bool>(filterExpression);
    

    所以,如果有人能帮助你提出一些想法或建议,那就太好了!

    2 回复  |  直到 9 年前
        1
  •  1
  •   Rafal    9 年前

    分析此测试:

    class Replaced
    {
        public Inner Inner { get; set; }
    }
    
    class Inner
    {
        public string Name { get; set; }
    }
    
    class Replacing
    {
        public Inner Inner { get; set; }
    }
    
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            var parameter = Expression.Parameter(typeof(Replacing));
            var visitor = new CustomVisitor(parameter);
            Expression<Func<Replaced, bool>> expression = x => x.Inner.Name == "ss";
            var resultExpression = (Expression<Func<Replacing, bool>>)visitor.Visit(expression);
    
            var function = resultExpression.Compile();
            var result = function(new Replacing
             {
                 Inner = new Inner
                 {
                     Name = "ss"
                 }
             });
    
            Assert.IsTrue(result);
        }
    }
    
    internal class CustomVisitor : ExpressionVisitor
    {
        private readonly ParameterExpression mParameter;
    
        private int counter = 0;
    
        public CustomVisitor(ParameterExpression parameter)
        {
            mParameter = parameter;
        }
    
        protected override Expression VisitLambda<T>(Expression<T> node)
        {
           return Expression.Lambda(
              Visit(node.Body), 
              node.Parameters.Select(x => (ParameterExpression)Visit(x)).ToArray());
    //or simpler but less generic        
    //return Expression.Lambda(Visit(node.Body), mParameter);
        }
    
        //this method will be called twice first for Name and then for Inner
        protected override Expression VisitMember(MemberExpression node)
        {
            counter++;
            System.Diagnostics.Debug.WriteLine("{0} - {1}", node.ToString(), counter);
    
            if (node.Member.MemberType != System.Reflection.MemberTypes.Property)
                throw new NotImplementedException();
    
            var memberName = node.Member.Name;
            var inner = Visit(node.Expression);
            var otherMember = inner.Type.GetProperty(memberName);
            return Expression.Property(inner, otherMember);
        }
    
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return mParameter;
        }
    }
    

    请注意,访问成员被呼叫两次,并且必须对两次呼叫做出相应的反应。此外,您还需要重写lambda创建,因为它将在参数替换中失败。

    PS:永远不要捕捉基类异常,这是一种糟糕的做法,异常时的恐慌返回null是错误的。

        2
  •  1
  •   Community    7 年前

    借助@Rafal的宝贵帮助和来自 this ,我设法实现了满足我需求的解决方案

    public static class EXpressionTreeTools
    {
        #region ConvertTypesInExpression
    
        /// <summary>
        /// Converts the types in the expression.
        /// </summary>
        /// <typeparam name="TSource">The source type (the "replacee").</typeparam>
        /// <typeparam name="TDestin">The destiny type (the replacer).</typeparam>
        /// <typeparam name="T">The type of the result fo the expression evaluation</typeparam>
        /// <param name="source">The source expression.</param>
        /// <returns></returns>
        public static Expression<Func<TDestin, T>> ConvertTypesInExpression<TSource, TDestin, T>(Expression<Func<TSource, T>> source)
        {
            var parameter = Expression.Parameter(typeof(TDestin));
            var visitor = new CustomVisitor(parameter);
            //Expression<Func<TSource, bool>> expression = x => x.Inner.Name == "ss";
            Expression<Func<TDestin, T>> resultExpression = (Expression<Func<TDestin, T>>)visitor.Visit(source);
    
            return resultExpression;
        }
    
        #endregion
    
        #region CustomVisitor
    
        /// <summary>
        /// A custom "visitor" class to traverse expression trees
        /// </summary>
        /// <typeparam name="T"></typeparam>
        internal class CustomVisitor : ExpressionVisitor
        {
            private readonly ParameterExpression mParameter;
    
            public CustomVisitor(ParameterExpression parameter)
            {
                mParameter = parameter;
            }
    
            protected override Expression VisitLambda<T>(Expression<T> node)
            {
                return Expression.Lambda(
                   Visit(node.Body),
                   node.Parameters.Select(x => (ParameterExpression)Visit(x)).ToArray());
                //or simpler but less generic        
                //return Expression.Lambda(Visit(node.Body), mParameter);
            }
    
            //this method will be called twice first for Name and then for Inner
            protected override Expression VisitMember(MemberExpression node)
            {
                if (node.Member.MemberType != System.Reflection.MemberTypes.Property)
                //throw new NotImplementedException();
                {
                    Expression exp = this.Visit(node.Expression);
    
                    if (exp == null || exp is ConstantExpression) // null=static member
                    {
                        object @object = exp == null ? null : ((ConstantExpression)exp).Value;
                        object value = null; Type type = null;
                        if (node.Member is FieldInfo)
                        {
                            FieldInfo fi = (FieldInfo)node.Member;
                            value = fi.GetValue(@object);
                            type = fi.FieldType;
                        }
                        else if (node.Member is PropertyInfo)
                        {
                            PropertyInfo pi = (PropertyInfo)node.Member;
                            if (pi.GetIndexParameters().Length != 0)
                                throw new ArgumentException("cannot eliminate closure references to indexed properties");
                            value = pi.GetValue(@object, null);
                            type = pi.PropertyType;
                        }
                        return Expression.Constant(value, type);
                    }
                    else // otherwise just pass it through
                    {
                        return Expression.MakeMemberAccess(exp, node.Member);
                    }
                }
                var memberName = node.Member.Name;
                var inner = Visit(node.Expression);
                var otherMember = inner.Type.GetProperty(memberName);
                return Expression.Property(inner, otherMember);
            }
    
            protected override Expression VisitParameter(ParameterExpression node)
            {
                return mParameter;
            }
        }
    
        #endregion
    }