C# .Net学习笔记—— Expression 表达式目录树

一、什么是表达式目录树

(1)Expression我们称为是表达式树,是一种数据结构体,用于存储需要计算,运算的一种结构,这种结构可以只是存储,而不进行运算。通常表达式目录树是配合Lambda一起来使用的,lambda可以是匿名方法,当然也可以使用Expression来动态的创建!

二、Func与Expression的区别

1、Func是方法

 Func func = (m, n) => m * n + 2;
 Console.WriteLine(func.Invoke(1, 1)); 
//运算:1*1+2=3

 2、Expression是数据结构

//lambda表达式声明表达式目录树
Expression> exp = (m, n) => m * n + 2;
int result = exp.Compile().Invoke(1, 2);
Console.WriteLine(result); 
//运算:1*2+2=4

注意:Expression只能为1行(如下图会报错)

3、使用ILSpy反编译解析看一下

C# .Net学习笔记—— Expression 表达式目录树_第1张图片

C# .Net学习笔记—— Expression 表达式目录树_第2张图片

调一下格式更好看一点

            ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
            ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n");

            var multiply = Expression.Multiply(parameterExpression, parameterExpression2);
            var constant = Expression.Constant(2,typeof(int));
            var add = Expression.Add(multiply, constant);

            Expression> exp =
                Expression.Lambda>(
                        add,
                        new ParameterExpression[2]
                        {
                            parameterExpression, parameterExpression2
                        });
打印看看结果
      int result = exp.Compile().Invoke(11, 12);
            Console.WriteLine(result);

得到134,与m*n+2得出结果一致

4、拼装练习

(1)练习一:

C# .Net学习笔记—— Expression 表达式目录树_第3张图片

(2)练习二

C# .Net学习笔记—— Expression 表达式目录树_第4张图片

(3)练习三

C# .Net学习笔记—— Expression 表达式目录树_第5张图片

5、动态生成硬编码(通用、性能好)

需求:我希望复制一个People出来

    public class People 
    {
        public int Age;
        public string Name;
    }
    public class PeopleCopy 
    {
        public int Age;
        public string Name;
    }

方法1:通过硬编码直接赋值

People people = new People()
{
   Age = 18,
   Name = "吴彦祖"
};
PeopleCopy peopleCopy = new PeopleCopy()
{
   Age = people.Age,
   Name = people.Name,
};

方法2:通过反射赋予

方法3:通过Json序列化与反序列化赋值

第一种方法性能最好,但是不够通用。方法2和方法3性能不好。

方法4:

这时候可以考虑使用表达式目录树来动态生成硬编码

思路:用表达目录树动态生成硬编码,生成保存到字典里,下次再调用的时候则直接从字典里拿。

 public class ExpressionMapper
    {
        private static Dictionary _dic = new Dictionary();

        public static TOut Trans(TIn tIn) 
        {
            string key = string.Format("funckey_{0}_{1}", typeof(TIn).FullName, typeof(TOut).FullName);
            if (!_dic.ContainsKey(key))
            {
                ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
                List memberBindingList = new List();

                foreach (var item in typeof(TOut).GetProperties())
                {
                    MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
                    MemberBinding memberBinding = Expression.Bind(item, property);
                    memberBindingList.Add(memberBinding);
                }

                foreach (var item in typeof(TOut).GetFields())
                {
                    MemberExpression field = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
                    MemberBinding memberBinding = Expression.Bind(item, field);
                    memberBindingList.Add(memberBinding);
                }
                MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
                Expression> lambda = Expression.Lambda>(memberInitExpression, new ParameterExpression[]
                {
                    parameterExpression
                });
                _dic[key] = lambda.Compile();
            }
            return ((Func)_dic[key]).Invoke(tIn);
        }

方法5:泛型缓存(相比方法4可以节省读取字典时候的消耗)

 public class ExpressionGenericMapper
    {
        private static Func _Func;
        static ExpressionGenericMapper()
        {
            ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
            List memberBindingList = new List();

            foreach (var item in typeof(TOut).GetProperties())
            {
                MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                memberBindingList.Add(memberBinding);
            }

            foreach (var item in typeof(TOut).GetFields())
            {
                MemberExpression field = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, field);
                memberBindingList.Add(memberBinding);
            }
            MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
            Expression> lambda = Expression.Lambda>(memberInitExpression, new ParameterExpression[]
            {
                parameterExpression
            });
            _Func = lambda.Compile();
        }

        public static TOut Trans(TIn t) 
        {
            return _Func(t);
        }

看一下字典缓存和泛型类缓存消耗的时间,明显可以看到通过泛型类缓存的性能更好,因为节省了查找字典的性能消耗。

         PrintExecutionTime(() =>
            {
                Console.WriteLine("通过字典缓存,第一次消耗时间:");
                PeopleCopy copy = ExpressionMapper.Trans(new People() { Age = 10, Name = "哇哈哈" });
            });
            PrintExecutionTime(() =>
            {
                Console.WriteLine("通过字典缓存,第二次消耗时间:");
                PeopleCopy copy2 = ExpressionMapper.Trans(new People() { Age = 10, Name = "哇哈哈" });
            });


            PrintExecutionTime(() =>
            {
                Console.WriteLine("通过泛型类缓存,第一次消耗时间:");
                PeopleCopy copy3 = ExpressionGenericMapper.Trans(new People() { Age = 11, Name = "啦啦啦" });
            });
            PrintExecutionTime(() =>
            {
                Console.WriteLine("通过泛型类缓存,第二次消耗时间:");
                PeopleCopy copy4 = ExpressionGenericMapper.Trans(new People() { Age = 11, Name = "啦啦啦" });
            });

C# .Net学习笔记—— Expression 表达式目录树_第6张图片

5、表达式目录树动态生成的用途:

可以用来替代反射,因为反射可以通用,但是性能不够

可以生成硬编码,可以提升性能

6、递归解析表达式目录树

(1)ExpressionVisitor:肯定得递归解析表达式目录树,因为不知道深度的一棵树

(2)只有一个入口叫Visit

(3)首先检查是个什么类型的表达式,然后调用对应的protected virtual

(4)得到结果继续去检查类型——调用对应的Visit方法——再检测——再调用。。。

案例:解析(m*n+2)

第一步(把m*n+2传入,入口时Visit)C# .Net学习笔记—— Expression 表达式目录树_第7张图片

第二步:检测到二元表达式,m*n+2进入VisitBinary方法.

node.Left为m*n(这里会再次调用VisitBinary方法)    node.Right为2

C# .Net学习笔记—— Expression 表达式目录树_第8张图片

  第三步:m*n也时二元表达式,因此重新进入VisitBinary方法

node.Left为m(进入VisitParameter方法)  node.Right为n(进入VisitParameter方法)

第四步:m*n解析完开始解析2,会进入VisitConstant方法

基础应用:我们可以把表达式的所有+号变成-号C# .Net学习笔记—— Expression 表达式目录树_第9张图片

从下图可以发现,表达式expression变成了m*n-2

C# .Net学习笔记—— Expression 表达式目录树_第10张图片

7、表达式拼接

using System.Linq.Expressions;

namespace ConsoleApp1
{
    public static class ExpressionExtend
    {
        public static Expression> And(this Expression> expr1, Expression> expr2) 
        {
            //return Expression.Lambda>(Expression.AndAlso(expr1.Body, expr2.Body));
            ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
            NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter) ;
            var left = visitor.Replace(expr1.Body);
            var right = visitor.Replace(expr2.Body);
            var body = Expression.And(left, right);
            return Expression.Lambda>(body, newParameter);
        }

        public static Expression> Or> expr1, Expression> expr2) 
        {
            ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
            NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
            var left = visitor.Replace(expr1.Body);
            var right = visitor.Replace(expr2.Body);
            var body = Expression.Or(left, right);
            return Expression.Lambda>(body, newParameter);
        }

        public static Expression> Not(this Expression> expr) 
        {
            var candidateExpr = expr.Parameters[0];
            var body = Expression.Not(expr.Body);
            return Expression.Lambda>(body, candidateExpr);
        }
    }

    public class NewExpressionVisitor : ExpressionVisitor
    {
        public ParameterExpression _NewParameter { get; private set; }
        public NewExpressionVisitor(ParameterExpression param)
        {
            this._NewParameter = param;
        }
        public Expression Replace(Expression exp)
        {
            return this.Visit(exp);
        }
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return this._NewParameter;
        }
    }
}
       public static void Show()
        {
            Expression> lambdal = x => x.Age > 5;
            Expression> lambda2 = x => x.ID > 5;
            Expression> lambda3 = lambdal.And(lambda2);
            Expression> lambda4 = lambdal.Or(lambda2);
            Expression> lambda5 = lambdal.Not();

            var peopleList = Do(lambda3);
            for (int i = 0; i < peopleList.Count; i++)
            {
                Console.WriteLine(peopleList[i].Name);
            }
        }

        private static List Do(Expression> func)
        {
            List people = new List()
            {
                new People() { ID = 1, Age = 10, Name = "老一" },
                new People() { ID = 2, Age = 20, Name = "老二" },
                new People() { ID = 3, Age = 60, Name = "老三" },
                new People() { ID = 4, Age = 18, Name = "老四" },
                new People() { ID = 5, Age = 10, Name = "哈哈哈" },
                new People() { ID = 6, Age = 15, Name = "方式" },
                new People() { ID = 7, Age = 10, Name = "老六啦啦啦啦啦啦啦" },
            };
            return people.Where(func.Compile()).ToList();
        }

调试成功,ID>5且age>5的只有这两个人。

表达式树也可以通过这种办法进行查询操作

你可能感兴趣的:(学习,笔记)