C#表达式目录树示例详解
1、表达式目录树
表达式目录树,在C#中是Expression来定义的,它是一种语法树,或者说是一种数据结构。其主要用于存储需要计算、运算的一种结构,它只提供存储功能,不进行运算。通常Expression是配合Lambda一起使用,lambda可以是匿名方法。Expression可以动态创建。
声明一个lambda表达式,其中可以指明类型,也可以是匿名方法:
//Funcfunc=newFunc ((m,n)=>m*n+2); Func func=(m,n)=>m*n+2;
上述代码可以使用Expression来定义:
Expression>exp=(m,n)=>m*n+2;//lambda表达式声明表达式目录树
Expression的方法体只能是一个整体,不能具有花括号,以下代码是不允许的:
Expression>exp1=(m,n)=>//方法体只能一体 { returnm*n+2; };
上述func和exp执行结果相同:
intiResult1=func.Invoke(3,2); intiResult2=exp.Compile().Invoke(3,2);
2、构建表达式目录树
上述表达式示例可以通过Expression来自主构建,把m、n定义为ParameterExpression参数,把2定义为常数表达式ConstantExpression,使用Expression的静态方法,表示乘和加:
ParameterExpressionparameterLeft=Expression.Parameter(typeof(int),"m");//定义参数 ParameterExpressionparameterRight=Expression.Parameter(typeof(int),"n");//定义参数 BinaryExpressionbinaryMultiply=Expression.Multiply(parameterLeft,parameterRight);//组建第一步的乘法 ConstantExpressionconstant=Expression.Constant(2,typeof(int));//定义常数参数 BinaryExpressionbinaryAdd=Expression.Add(binaryMultiply,constant);//组建第二步的加法 varexpression=Expression.Lambda>(binaryAdd,parameterLeft,parameterRight);//构建表达式 varfunc=expression.Compile();//编译为lambda表达式 intiResult3=func(3,2); intiResult4=expression.Compile().Invoke(3,2); intiResult5=expression.Compile()(3,2);
自主构建Expression是,参数名称的定义,可以不是m、n,可以是其他的a、b或者x、y。
如何构建一个复杂的表达式目录树?需要使用到Expression中更多的方法、属性、扩展方法等。首先定义一个类:
publicclassPeople { publicintAge{get;set;} publicstringName{get;set;} publicintId; }
基于上面的类,构建表达式:Expression
这个示例中,使用到了int自身的ToString()方法,还使用到了字符串的Equals方法。构建过程如下:
//以下表达式目录树实现lambda的表达式 Expression>lambda=x=>x.Id.ToString().Equals("5"); //声明一个参数对象 ParameterExpressionparameterExpression=Expression.Parameter(typeof(People),"x"); //查找字段,并绑定访问参数对象字段(属性)的方法:x.Id MemberExpressionmember=Expression.Field(parameterExpression,typeof(People).GetField("Id")); //以上可以用这个代替 vartemp=Expression.PropertyOrField(parameterExpression,"Id"); //调用字段的ToString方法:x.Id.ToString() MethodCallExpressionmethod=Expression.Call(member,typeof(int).GetMethod("ToString",newType[]{}),newExpression[0]); //调用字符串的Equals方法:x.Id.ToString().Equals("5") MethodCallExpressionmethodEquals=Expression.Call(method,typeof(string).GetMethod("Equals",newType[]{typeof(string)}),newExpression[] { Expression.Constant("5",typeof(string))//与常量进行比较,也可以是参数 }); //创建目录树表达式 arexpression=Expression.Lambda >(methodEquals,newParameterExpression[]{parameterExpression}); boolbResult=expression.Compile().Invoke(newPeople() { Id=5, Name="Nigle", Age=31 });
3、使用Expression来进行不同对象的相同名字的属性映射
前面构建了类People,现在我们构建一个新的类PeopleCopy:
publicclassPeopleCopy { publicintAge{get;set;} publicstringName{get;set;} publicintId; }
现在声明一个People对象,然后对People对象的数据进行拷贝到PeopleCopy新对象中去,直接硬编码的方式:
1.硬编码
Peoplepeople=newPeople() { Id=11, Name="Nigle", Age=31 }; PeopleCopypeopleCopy=newPeopleCopy() { Id=people.Id, Name=people.Name, Age=people.Age };
如果这样编写,对于属性或者字段比较多的类,在拷贝时,我们需要编写很多次赋值,代码也会很长。此时,我们能想到的是通过反射的方式进行拷贝:
2.反射拷贝
publicstaticTOutTrans(TIntIn) { TOuttOut=Activator.CreateInstance (); foreach(varitemOutintOut.GetType().GetProperties()) { foreach(varitemInintIn.GetType().GetProperties()) { if(itemOut.Name.Equals(itemIn.Name)) { itemOut.SetValue(tOut,itemIn.GetValue(tIn)); break; } } } foreach(varitemOutintOut.GetType().GetFields()) { foreach(varitemInintIn.GetType().GetFields()) { if(itemOut.Name.Equals(itemIn.Name)) { itemOut.SetValue(tOut,itemIn.GetValue(tIn)); break; } } } returntOut; }
通过反射,我们可以通过输出类型的属性或者字段去查找原类型对应的属性和字段,然后获取值,并设置值的方式进行赋值拷贝。除此之外,我们还能想到的是深克隆的序列化方式,进行反序列化数据:
3.序列化和反序列化
publicclassSerializeMapper { ///序列化反序列化方式/summary> publicstaticTOutTrans (TIntIn) { //采用的是json序列化,也可以采用其他序列化方式 returnJsonConvert.DeserializeObject (JsonConvert.SerializeObject(tIn)); } }
前面的三种方法是最为常用的方法,但未使用到本文介绍的表达式目录树。如何将表达式目录树与拷贝结合起来?有两种方式【缓存+表达式目录】,【泛型+表达式目录】
4.缓存+表达式目录
//////生成表达式目录树缓存 /// publicclassExpressionMapper { privatestaticDictionary_Dic=newDictionary (); /// ///字典缓存表达式树 /// publicstaticTOutTrans(TIntIn) { stringkey=string.Format("funckey_{0}_{1}",typeof(TIn).FullName,typeof(TOut).FullName); if(!_Dic.ContainsKey(key)) { ParameterExpressionparameterExpression=Expression.Parameter(typeof(TIn),"p"); List memberBindingList=newList (); foreach(varitemintypeof(TOut).GetProperties()) { MemberExpressionproperty=Expression.Property(parameterExpression,typeof(TIn).GetProperty(item.Name)); //绑定Out和In之间的关系:Age=p.Age MemberBindingmemberBinding=Expression.Bind(item,property); memberBindingList.Add(memberBinding); } foreach(varitemintypeof(TOut).GetFields()) { MemberExpressionproperty=Expression.Field(parameterExpression,typeof(TIn).GetField(item.Name)); MemberBindingmemberBinding=Expression.Bind(item,property); memberBindingList.Add(memberBinding); } MemberInitExpressionmemberInitExpression=Expression.MemberInit(Expression.New(typeof(TOut)),memberBindingList.ToArray()); Expression >lambda=Expression.Lambda >(memberInitExpression,parameterExpression); Func func=lambda.Compile();//拼装是一次性的 _Dic[key]=func; } return((Func )_Dic[key]).Invoke(tIn); } }
5.泛型+表达式目录
//////生成表达式目录树泛型缓存 /// ////// publicclassExpressionGenericMapper //Mapper`2 { privatestaticFunc func=null; staticExpressionGenericMapper() { ParameterExpressionparameterExpression=Expression.Parameter(typeof(TIn),"p"); List memberBindingList=newList (); foreach(varitemintypeof(TOut).GetProperties()) { MemberExpressionproperty=Expression.Property(parameterExpression,typeof(TIn).GetProperty(item.Name)); MemberBindingmemberBinding=Expression.Bind(item,property); memberBindingList.Add(memberBinding); } foreach(varitemintypeof(TOut).GetFields()) { MemberExpressionproperty=Expression.Field(parameterExpression,typeof(TIn).GetField(item.Name)); MemberBindingmemberBinding=Expression.Bind(item,property); memberBindingList.Add(memberBinding); } MemberInitExpressionmemberInitExpression=Expression.MemberInit(Expression.New(typeof(TOut)),memberBindingList.ToArray()); Expression >lambda=Expression.Lambda >(memberInitExpression,newParameterExpression[] { parameterExpression }); func=lambda.Compile();//拼装是一次性的 } publicstaticTOutTrans(TInt) { returnfunc(t); } }
除了上述5中方法,还可以使用框架自带的AutoMapper,首先我们要nuget添加引用AutoMapper即可直接使用,具体代码为:
6.AutoMapper
publicclassAutoMapperTest { publicstaticTOutTrans(TIntIn) { returnAutoMapper.Mapper.Instance.Map (tIn); } }
测评:对上述6种方式进行测评,每一种拷贝方式运行1000000次:
Stopwatchwatch=newStopwatch(); watch.Start(); for(inti=0;i<1000000;i++) { //测试六种方法 PeopleCopypeopleCopy=newPeopleCopy(){Id=people.Id,Name=people.Name,Age=people.Age};//直接赋值的方式复制--22ms //PeopleCopypeopleCopy=ReflectionMapper.Trans(people);//反射赋值的方式复制---1573ms //PeopleCopypeopleCopy=SerializeMapper.Trans (people);//序列化方式---2716ms //PeopleCopypeopleCopy=ExpressionMapper.Trans (people);//表达式目录树缓存复制---517ms //PeopleCopypeopleCopy=ExpressionGenericMapper .Trans(people);//表达式目录树泛型缓存--77ms //PeopleCopypeopleCopy=AutoMapperTest.Trans (people);//AutoMapper---260ms } watch.Stop(); Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}ms");
4、表达式目录树构建SQL删选
传统的sql在构建条件语句时,需要通过诸多判断,进而构建成完整的查询语句。
Peoplep=newPeople() { Id=11, Name="Nigle", Age=31 }; //拼装sql的方式 stringsql="SELECT*FROMUSERWHEREId=1"; if(string.IsNullOrWhiteSpace(p.Name)) { sql+=$"andnamelike'%{p.Name}%'"; } sql+=$"andage>{p.Age}";
事实上,我们偶尔我们会使用linq查询或者lambda表达式,用于条件筛选,如varlambda=x=>x.Age>5;事实上,我们可以构建上述Expression:
Peoplep=newPeople() { Id=11, Name="Nigle", Age=31 }; //拼装表达式目录树,交给下端用 ParameterExpressionparameterExpression=Expression.Parameter(typeof(People),"x");//声明一个参数 ExpressionpropertyExpression=Expression.Property(parameterExpression,typeof(People).GetProperty("Age"));//声明访问参数属性的对象 //Expressionproperty=Expression.Field(parameterExpression,typeof(People).GetField("Id")); ConstantExpressionconstantExpression=Expression.Constant(5,typeof(int));//声明一个常量 BinaryExpressionbinary=Expression.GreaterThan(propertyExpression,constantExpression);//添加比较方法 varlambda=Expression.Lambda>(binary,newParameterExpression[]{parameterExpression});//构建表达式主体 boolbResult=lambda.Compile().Invoke(p);//比较值
5、修改表达式目录树
本示例将把已经构建完成的表达式目录树的加法进行修改为减法。修改、拼接、读取节点,需要使用到ExpressionVisitor类,ExpressionVisitor类能动态的解耦,读取相关的节点和方法。
ExpressionVisitor类中的Visit(Expressionnode)是解读表达式的入口,然后能够神奇的区分参数和方法体,然后将表达式调度到此类中更专用的访问方法中,然后一层一层的解析下去,直到最终的叶节点!
首先编写OperationsVisitor类,用于修改:
internalclassOperationsVisitor:ExpressionVisitor { publicExpressionModify(Expressionexpression) { returnthis.Visit(expression); } protectedoverrideExpressionVisitBinary(BinaryExpressionb) { if(b.NodeType==ExpressionType.Add) { Expressionleft=this.Visit(b.Left); Expressionright=this.Visit(b.Right); returnExpression.Subtract(left,right); } returnbase.VisitBinary(b); } protectedoverrideExpressionVisitConstant(ConstantExpressionnode) { returnbase.VisitConstant(node); } }
然后,编写lambda表达式,进行修改并计算结果:
//修改表达式目录树 Expression>exp=(m,n)=>m*n+2; OperationsVisitorvisitor=newOperationsVisitor(); ExpressionexpNew=visitor.Modify(exp); int?iResult=(expNewasExpression >)?.Compile().Invoke(2,3);
Visit这个这个方法能够识别出来m*n+2是个二叉树,会通过下面的图然后一步一步的进行解析,如果遇到m*n这会直接调用VisitBinary(BinaryExpressionb)这个方法,如果遇到m或者n会调用VisitParameter(ParameterExpressionnode)这个方法,如果遇到2常量则会调用VisitConstant(ConstantExpressionnode)。
ORM与表达式树目录的关系:
经常用到EF,其实都是继承Queryable,然后我们使用的EF通常都会使用varitems=anserDo.GetAll().Where(x=>x.OrganizationId==input.oid||input.oid==0),where其实传的就是表达式目录树。EF写的where等lambda表达式,就是通过ExpressionVisitor这个类来反解析的!后面将构建模拟EF的解析方法。
6、构建模拟EF的表达式目录树解析
首先,构建解析表达式目录树的方法,不能再使用默认的。
//////表达式目录树中的访问者 /// internalclassConditionBuilderVisitor:ExpressionVisitor { //////用于存放条件等数据 /// privateStack_StringStack=newStack (); /// /// /// ///internalstringCondition() { stringcondition=string.Concat(this._StringStack.ToArray()); this._StringStack.Clear(); returncondition; } /// ///如果是二元表达式 /// ////// protectedoverrideExpressionVisitBinary(BinaryExpressionnode) { if(node==null)thrownewArgumentNullException("BinaryExpression"); this._StringStack.Push(")"); base.Visit(node.Right);//解析右边 this._StringStack.Push(""+ToSqlOperator(node.NodeType)+""); base.Visit(node.Left);//解析左边 this._StringStack.Push("("); returnnode; } /// /// /// ////// protectedoverrideExpressionVisitMember(MemberExpressionnode) { if(node==null) thrownewArgumentNullException("MemberExpression"); this._StringStack.Push("["+node.Member.Name+"]"); returnnode; returnbase.VisitMember(node); } /// ///将节点类型转换为Sql的操作符 /// ////// stringToSqlOperator(ExpressionTypetype) { switch(type) { case(ExpressionType.AndAlso): case(ExpressionType.And): return"AND"; case(ExpressionType.OrElse): case(ExpressionType.Or): return"OR"; case(ExpressionType.Not): return"NOT"; case(ExpressionType.NotEqual): return"<>"; caseExpressionType.GreaterThan: return">"; caseExpressionType.GreaterThanOrEqual: return">="; caseExpressionType.LessThan: return"<"; caseExpressionType.LessThanOrEqual: return"<="; case(ExpressionType.Equal): return"="; default: thrownewException("不支持该方法"); } } /// ///常量表达式 /// ////// protectedoverrideExpressionVisitConstant(ConstantExpressionnode) { if(node==null) thrownewArgumentNullException("ConstantExpression"); this._StringStack.Push("'"+node.Value+"'"); returnnode; } /// ///方法表达式 /// ////// protectedoverrideExpressionVisitMethodCall(MethodCallExpressionm) { if(m==null)thrownewArgumentNullException("MethodCallExpression"); stringformat; switch(m.Method.Name) { case"StartsWith": format="({0}LIKE{1}+'%')"; break; case"Contains": format="({0}LIKE'%'+{1}+'%')"; break; case"EndsWith": format="({0}LIKE'%'+{1})"; break; default: thrownewNotSupportedException(m.NodeType+"isnotsupported!"); } this.Visit(m.Object); this.Visit(m.Arguments[0]); stringright=this._StringStack.Pop(); stringleft=this._StringStack.Pop(); this._StringStack.Push(String.Format(format,left,right)); returnm; } }
然后,外部就可以通过编写表达式目录树的查询条件,再通过这个类的实例进行解析成对应的SQL语句:
{ Expression>lambda=x=>x.Age>5&&x.Id>5 &&x.Name.StartsWith("1") &&x.Name.EndsWith("1") &&x.Name.Contains("2"); //“x=>x.Age>5&&x.Id>5”等同于sql语句 stringsql=string.Format("DeleteFrom[{0}]WHERE{1}",typeof(People).Name,"[Age]>5AND[ID]>5"); ConditionBuilderVisitorvistor=newConditionBuilderVisitor(); vistor.Visit(lambda); Console.WriteLine(vistor.Condition()); } { Expression >lambda=x=>x.Age>5&&x.Name=="A"||x.Id>5; ConditionBuilderVisitorvistor=newConditionBuilderVisitor(); vistor.Visit(lambda); Console.WriteLine(vistor.Condition()); } { Expression >lambda=x=>x.Age>5||(x.Name=="A"&&x.Id>5); ConditionBuilderVisitorvistor=newConditionBuilderVisitor(); vistor.Visit(lambda); Console.WriteLine(vistor.Condition()); } { Expression >lambda=x=>(x.Age>5||x.Name=="A")&&x.Id>5; ConditionBuilderVisitorvistor=newConditionBuilderVisitor(); vistor.Visit(lambda); Console.WriteLine(vistor.Condition()); }
7、连接表达式目录树
表达式目录树除了可以修改外,我们还可以通过对其进行表达式目录树的拼接,将两个及其以上的表达式目录树进行拼接在一起。先编写一个新的NewExpressionVisitor,继承自ExpressionVisitor,用于拼接时,调用的。它是一个内部类,放在访问拼接类的内部ExpressionExtend。然后再编写对应的扩展方法:Add、Or、Not
//////合并表达式AndOrNot扩展 /// publicstaticclassExpressionExtend { ///合并表达式expLeftandexpRight publicstaticExpression>And (thisExpression >expLeft,Expression >expRight) { //用于将参数名进行替换,二者参数不一样 ParameterExpressionnewParameter=Expression.Parameter(typeof(T),"c"); NewExpressionVisitorvisitor=newNewExpressionVisitor(newParameter); //需要先将参数替换为一致的,可能参数名不一样 varleft=visitor.Replace(expLeft.Body);//左侧的表达式 varright=visitor.Replace(expRight.Body);//右侧的表达式 varbody=Expression.And(left,right);//合并表达式 returnExpression.Lambda >(body,newParameter); } /// 合并表达式expr1orexpr2 publicstaticExpression>Or (thisExpression >expr1,Expression >expr2) { ParameterExpressionnewParameter=Expression.Parameter(typeof(T),"c"); NewExpressionVisitorvisitor=newNewExpressionVisitor(newParameter); //需要先将参数替换为一致的,可能参数名不一样 varleft=visitor.Replace(expr1.Body); varright=visitor.Replace(expr2.Body); varbody=Expression.Or(left,right); returnExpression.Lambda >(body,newParameter); } publicstaticExpression >Not (thisExpression >expr) { varcandidateExpr=expr.Parameters[0]; varbody=Expression.Not(expr.Body); returnExpression.Lambda >(body,candidateExpr); } /// 参数替换者 classNewExpressionVisitor:ExpressionVisitor { publicParameterExpression_NewParameter{get;privateset;} publicNewExpressionVisitor(ParameterExpressionparam) { this._NewParameter=param;//用于把参数替换了 } ///替换 publicExpressionReplace(Expressionexp) { returnthis.Visit(exp); } protectedoverrideExpressionVisitParameter(ParameterExpressionnode) { //返回新的参数名 returnthis._NewParameter; } } }
下面是测试代码:
Expression>lambda1=x=>x.Age>5; Expression >lambda2=p=>p.Id>5; Expression >lambda3=lambda1.And(lambda2); Expression >lambda4=lambda1.Or(lambda2); Expression >lambda5=lambda1.Not(); List people=newList () { newPeople(){Id=4,Name="123",Age=4}, newPeople(){Id=5,Name="234",Age=5}, newPeople(){Id=6,Name="345",Age=6}, }; List lst1=people.Where(lambda3.Compile()).ToList(); List lst2=people.Where(lambda4.Compile()).ToList(); List lst3=people.Where(lambda5.Compile()).ToList();
总结
到此这篇关于C#表达式目录树的文章就介绍到这了,更多相关C#表达式目录树内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。