主要内容概要
1 什么是表达式目录树Expression
2 动态拼装Expression
3 基于Expression扩展应用
4 ExpressionVisitor解析表达式目录树
5 解析Expression生成Sql
6 Expression扩展拼装链接
表达式目录树Expression
先看一下在linq中的两个例子:
new List<int>().Where(a => a > 10); //Linq To Object
new List<People>().AsQueryable().Where(a => a.Id > 10 && a.Name.ToString().Contains("Hyl")); //Linq To Sql
对比一下:Func<int, int, int> func = (m, n) => m * n + 2;
lambda实例化委托 匿名方法;Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;
ExpresssionFunc<int, int, int>
;
结合下面这个图看一下这里面的概念:
声明一个表达式目录树;
快捷方式的声明 数据结构;
类似于一个树形结构,描述不同变量和常量之间的关系 数据结构;
表达式目录树:语法树,或者说是一种数据结构;
PS: 如果在调试的时候需要看表达式的具体内容,可以通过这个工具ExpressionTreeVisualizer。
另外,Expression中不能有语句体,即只能有一行,不能有多行。
表达式目录树变成委托
通过Compile()方法可以将目录树变成委托:
int iResult1 = func.Invoke(12, 23);
//exp.Compile()=> 委托;
int iResult2 = exp.Compile().Invoke(12, 23); //12 * 23 +2 278
手动拼装表达式目录树
- 首先来一个简答一点的拼装,就实现123+234这个加法:
//Expression<Func<int>> expression = () => 123 + 234;
ConstantExpression left = Expression.Constant(123, typeof(int));
ConstantExpression right = Expression.Constant(234, typeof(int));
var plus = Expression.Add(left, right);
Expression<Func<int>> expression = Expression.Lambda<Func<int>>(plus, new ParameterExpression[0]);
int iResult = expression.Compile().Invoke();
int iResult1 = expression.Compile()();
其中,这个Expression.Lambda方法是这样的public static Expression<TDelegate> Lambda<TDelegate>(Expression body,params ParameterExpression[] parameters)
,返回一个泛型的表达式目录树。
- 然后再来个稍微复杂点的表达式目录树拼装例子:
//Expression<Func<int, int, int>> expression = (m, n) => m * n + m + n + 2;
ParameterExpression m = Expression.Parameter(typeof(int), "m");
ParameterExpression n = Expression.Parameter(typeof(int), "n");
ConstantExpression constant2 = Expression.Constant(2, typeof(int));
var multiply = Expression.Multiply(m, n);
var plus1 = Expression.Add(multiply, m);
var plus2 = Expression.Add(plus1, n);
var plus = Expression.Add(plus2, constant2);
Expression<Func<int, int, int>> expression = Expression.Lambda<Func<int, int, int>>(plus, new ParameterExpression[]
{
m,
n
});
int iResult = expression.Compile().Invoke(12, 10);
- 再来一个不是加减乘除的:
整个过程就是按照Lambda表达式的内容来写的// Expression<Func<People, bool>> lambda = (x) => x.Id.ToString().Equals("5"); ParameterExpression x = Expression.Parameter(typeof(People), "x"); FieldInfo field = typeof(People).GetField("Id"); ConstantExpression constant5 = Expression.Constant("5"); var fieldExp = Expression.Field(x, field); MethodInfo toString = typeof(int).GetMethod("ToString", new Type[] { }); MethodInfo equals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) }); var tostringExp = Expression.Call(fieldExp, toString, new Expression[0]); var equalsExp = Expression.Call(tostringExp, equals, new Expression[] { constant5 }); Expression<Func<People, bool>> expression = Expression.Lambda<Func<People, bool>>(equalsExp, new ParameterExpression[] { x }); bool bResult = expression.Compile().Invoke(new People() { Id = 5, Name = "德玛西亚" });
x.Id.ToString().Equals("5");
。其中,在找方法的时候要添加Type选择无参的:GetMethod("ToString", new Type[] { })
动态
有一个场景是:以前根据用户输入拼装条件,用户输入名称,为空就跳过:
- 第一个时代:
通过sql语句来拼接。
string sql = "SELECT * FROM USER WHERE 1=1";
Console.WriteLine("用户输入个名称,为空就跳过");
string name = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(name))
{
sql += $" and name like '%{name}%'";
}
Console.WriteLine("用户输入个账号,为空就跳过");
string account = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(account))
{
sql += $" and account like '%{account}%'";
}
- 第二个时代:
界面上有好几处输入,多来几个条件就会没法写。
出现整个DBSet 暴露出来了,在使用的时候是非常危险。
//Linq To Sql
var DbSet = new List<People>().AsQueryable();//然后对表操作
//Expression<Func<People, bool>> exp = a => a.Name.Contains("Richard") && a.Id == 10;
Expression<Func<People, bool>> exp = null;
Console.WriteLine("用户输入个名称,为空就跳过");
string name = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(name))
{
//exp = a => a.Name.Contains(name);
//DbSet = DbSet.Where(a => a.Name.Contains(name));
}
Console.WriteLine("用户输入个账号,为空就跳过");
string account = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(account))
{
//exp = a => a.Account.Contains(account);
//DbSet = DbSet.Where(a => a.Account.Contains(account));// 出现整个DBSet 暴露出来了,在使用的时候是非常危险
}
exp = a => a.Account.Contains(account) && a.Name.Contains(name);
有两个解决方案:
- 以前有个表达式树的扩展,扩展了and 和 or,基于visitor实现
- 根据字符串和条件自动拼装出来
Expression<Func<People, bool>> exp1 = item => item.Account.Contains("Admin") && item.Name.Contains("Richard");
if (Account 不为空)
var Account = typeof(People).GetProperty("Account");
var contains = typeof(string).GetMethod("Contains");
ParameterExpression a = Expression.Parameter(typeof(People), "a");
Expression<Func<People, bool>> expression = Expression.Lambda<Func<People, bool>>(Expression.AndAlso(Expression.Call(Expression.Property(a, Account, (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(Contains())), new Expression[]
{
Expression.Constant("Admin", typeof(string))
}), Expression.Call(Expression.Property(a, (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(get_Name()))), (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(Contains())), new Expression[]
{
Expression.Constant("Richard", typeof(string))})), new ParameterExpression[]
{
a
});
这个过程可以根据用户输入,封装成一个表达式目录树的自动生成。
下面再看一个关于实体类和实体类DTO的例子,现有两个类People和PeopleCopy:
//Entity
public class People
{
public int Age { get; set; }
public string Name { get; set; }
public int Id;
}
// PeopleDTO
public class PeopleCopy
{
public int Age { get; set; }
public string Name { get; set; }
public int Id;
}
在做转换的时候我们是不可以强制转换的,要么是逐个赋值来进行转换的:
People people = new People()
{
Id = 11,
Name = "Richard",
Age = 31
};
PeopleCopy peopleCopy = new PeopleCopy()//硬编码 //硬编码性能好,但是通用型差
{
Id = people.Id,
Name = people.Name,
Age = people.Age
};
思考:如果说有其他别的类型需要转换,那么不是为所有的类型都需要写这样代码?
方法一: 反射+泛型方法
public class ReflectionMapper
{
public static TOut Trans<TIn, TOut>(TIn tIn)
{
TOut tOut = Activator.CreateInstance<TOut>();
foreach (var itemOut in tOut.GetType().GetProperties())
{
var propIn = tIn.GetType().GetProperty(itemOut.Name);
itemOut.SetValue(tOut, propIn.GetValue(tIn));
}
foreach (var itemOut in tOut.GetType().GetFields())
{
var fieldIn = tIn.GetType().GetField(itemOut.Name);
itemOut.SetValue(tOut, fieldIn.GetValue(tIn));
}
return tOut;
}
}
通过上述这个泛型反射的方法,可以把目标DTO的所有属性和字段在实体中找到并赋值,完成实体到DTO的的转换。
PeopleCopy peopleCopy1 = ReflectionMapper.Trans<People, PeopleCopy>(people);
方法二:序列化反序列化方式
public class SerializeMapper
{
public static TOut Trans<TIn, TOut>(TIn tIn)
{
return JsonConvert.DeserializeObject<TOut>(JsonConvert.SerializeObject(tIn));
}
}
通过将实体对象people序列化得到一个json,然后再反序列化成peoplecopy。
PeopleCopy peopleCopy2 = SerializeMapper.Trans<People, PeopleCopy>(people);
上面两个方法本质上都是通过反射来实现,在性能上肯定无法保证。那有没有什么方法既能保证通用又能保证性能呢?
Func<People, PeopleCopy> exp1 = p =>
{
return new PeopleCopy()
{
Id = p.Id,
Name = p.Name,
Age = p.Age
};
};
PeopleCopy peopleCopy3=func.Invoke(people);
上述过程提供了一个思路:想办法去动态拼装这个委托,然后缓存下委托,后面再次转换时就没有性能损耗了。
方法三: 泛型方法+表达式目录 = 既可以通用,效率高
public class ExpressionMapper
{
// 字典缓存--hash分布
private static Dictionary<string, object> _Dic = new Dictionary<string, object>(); //超过一定的数量之后 在字典中获取值就会有性能损耗
public static TOut Trans<TIn, TOut>(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<MemberBinding> memberBindingList = new List<MemberBinding>();
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 property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
{
parameterExpression
});
Func<TIn, TOut> func = lambda.Compile();//拼装是一次性的
_Dic[key] = func;
}
return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn);
}
}
通过动态地拼装委托,可以把表达式缓存起来,提高性能。但是字典缓存的方式是通过hash分布来寻址,当查找次数比较大的时候,会有性能问题。
方法四:泛型缓存+表达式目录
public class ExpressionGenericMapper<TIn, TOut>//Mapper`2 //正对于每两个不同类型的组合都会生成副本
{
private static Func<TIn, TOut> _FUNC = null;// 在每个副本中都有一个委托
static ExpressionGenericMapper() //静态构造函数 生成表达式目录树
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
List<MemberBinding> memberBindingList = new List<MemberBinding>();
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 property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)),memberBindingList.ToArray());
Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
{
parameterExpression
});
_FUNC = lambda.Compile();//拼装是一次性的 转换成委托以后放入副本的静态变量中去
}
public static TOut Trans(TIn t) // 直接获取副本的静态变量(委托)
{
return _FUNC(t);
}
}
泛型缓存的优点在第一小节已经演示过了,下面可以做个试验来验证上述四种方式的性能:
Console.WriteLine("****************************性能测试结果***************************");
People people = new People()
{
Id = 11,
Name = "Richard",
Age = 31
};
long common = 0;
long generic = 0;
long cache = 0;
long reflection = 0;
long serialize = 0;
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 100_000; i++)
{
PeopleCopy peopleCopy = new PeopleCopy()
{
Id = people.Id,
Name = people.Name,
Age = people.Age
};
}
watch.Stop();
common = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 100_000; i++)
{
PeopleCopy peopleCopy = ReflectionMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
reflection = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 100_000; i++)
{
PeopleCopy peopleCopy = SerializeMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
serialize = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 100_000; i++)
{
PeopleCopy peopleCopy = ExpressionMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
cache = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 100_000; i++)
{
PeopleCopy peopleCopy = ExpressionGenericMapper<People, PeopleCopy>.Trans(people);
}
watch.Stop();
generic = watch.ElapsedMilliseconds;
}
Console.WriteLine($"common = { common} ms");
Console.WriteLine($"reflection = { reflection} ms");
Console.WriteLine($"serialize = { serialize} ms");
Console.WriteLine($"cache = { cache} ms");
Console.WriteLine($"generic = { generic} ms");
结果显示:硬编码方法是27ms,反射方法是2195ms,序列化方法是3589ms,字典缓存方法是556ms,泛型缓存是91ms。
小结一下:
- 通过拼接表达式目录树+ 泛型缓存性能最高!
- 硬编码性能最高,为了通用,动态生成硬编码(表达式目录树拼装)==最完美
以上内容都是围绕表达式目录树的拼装来展开,下半部分是围绕表达式目录树的解析来展开的。
首先我们来想个问题:为什么在封装SQL查询的时候,各种查询条件没有具体实现呢,一般只有一个Id呢?因为写不出来。
属性可能是ID Name Accound State等;值也可能是int string datetime等类型;操作也可能是大于 小于 等于包含等;那么有可能是一个条件,两个条件,或者说N个条件。是没法写出来通用的封装方法的。
值得注意的是,这里我们不考虑传递sql语句,例如id=3这种。
如果非要解决这个问题,可以用个土办法,就是把需要的内容封装成一个对象,例如 colum operation value,然后接受一个集合,再去解析这个集合,执行操作,这是所谓土办法。
那么表达式目录树的出现就很好的解决了这个问题,它定义了一种数据结构;调用方会有各种的条件需要传递下去,底层需要解析调用传递的东西,所以需要一个数据结构(语法/约定),上端去组装,下端去解析。
再次回到刚才的问题,假设有public List<T> FindWhere<T>()
需要传递一些规则进去,就可以通过表达式目录树来实现:public List<T> FindWhere<T>(Expression<Func<T,bool>>expression)
,例如这里expression=x=>x.Id<5&& x.Age>5
去做约束。
那么问题来了:下是如何去解析表达式目录树呢?
ExpressionVisitor解析表达式目录树
ExpressionVisitor访问者类,Visit是一个入口,先判断,进一步的解析,然后
- lambada会区分参数和方法体,调度到更加专业的方法中解析;
- 根据表达式的类型,调度到更加专业的方法中解析;
- 默认根据旧的模式产生一个新的表达式目录树;也可以自行扩展,把一些解读操作进行变化。
- 表达式目录树是一个二叉树,visit做的事就是一层层地解析下去,一直到最终的叶节点。
还是回到这个图,现在是看如何解析这颗树,从根节点往下拆。
首先有个内置的ExpressionVisitor
这个类表示表达式树的访问者或重写者。
public class OperationsVisitor : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return this.Visit(expression);
}
public override Expression Visit(Expression node)
{
Console.WriteLine($"Visit {node.ToString()}");
return base.Visit(node);
}
protected override Expression VisitBinary(BinaryExpression b)
{
Console.WriteLine(b.ToString());
if (b.NodeType == ExpressionType.Add)
{
Expression left = this.Visit(b.Left); // 会产生一个新的表达式目录树
Expression right = this.Visit(b.Right);
Console.WriteLine(left.ToString());
Console.WriteLine(right.ToString());
return Expression.Subtract(left, right);
}
else if (b.NodeType==ExpressionType.Multiply)
{
Expression left = this.Visit(b.Left); // 会产生一个新的表达式目录树
Expression right = this.Visit(b.Right);
return Expression.Divide(left, right);
}
var express = base.VisitBinary(b);//默认的二元访问,其实什么都不干
return express;
}
protected override Expression VisitConstant(ConstantExpression node)
{
Console.WriteLine(node.ToString());
return base.VisitConstant(node);
}
}
然后具体使用:
//修改表达式目录树
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;
OperationsVisitor visitor = new OperationsVisitor();
//visitor.Visit(exp);
Expression expNew = visitor.Modify(exp);
分析一下这个过程:Visit是个入口,解读node表达式
根据表达式的类型,将表达式调度到此类中更专用的访问方法之一的表达式。
在本例中,读到是mn+2是一个二元表达式,base.Visit()方法就会调用这个VisitBinary()
方法。
本例的扩展可以检测二元的操作如果是相加就改成相减,如果是相乘就改成相除。
接着调用this.Visit()这个方法,继续调用base.visit()这个方法,即继续判断Expression的类型,是否是二元运算式,如果是的话,还会走到VisitBinary这个方法中,继续改掉运算符号。
最后再调用base.VisitBinary()这个默认的二元访问,什么的不干。
这样一来最后`mn+2变成了
m*n-2`这个表达式了。
Visit本身不做什么,就是去递归遍历,我们可以通过override在访问的时候去修改一些内容。这里的例子只是为了演示访问的过程,为下面的扩展应用做一些铺垫。
解析Expression生成Sql
思考一下:Visit的意义是什么?应用场景在哪?
写成这样的表达式目录树怎么变成sql呢?Expression<Func<People, bool>> lambda = x => x.Age > 5;
select * from People where Age>5
new List<People>().AsQueryable().Where(x => x.Age > 5 && x.Id == 10);
要完成这个sql需要三个要素,一个是age 一个是大于符号 一个是5 还有x是来自于泛型类型的。这就变成了要从表达式目录树里面获取这三要素。换句话说,这里是ORM的具体实现。
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
下面就来具体看下这个扩展的ConditionBuilderVisitor
的实现:
public class ConditionBuilderVisitor : ExpressionVisitor
{
private Stack<string> _StringStack = new Stack<string>();
public string Condition()
{
string condition = string.Concat(this._StringStack.ToArray());
this._StringStack.Clear();
return condition;
}
//((((( [Age] > 5) AND ( [Id] > 5)) AND ( [Name] LIKE '1%')) AND ( [Name] LIKE '%1')) AND ( [Name] LIKE '%1%'))
// 二元表达式
protected override Expression VisitBinary(BinaryExpression node)
{
if (node == null) throw new ArgumentNullException("BinaryExpression");
this._StringStack.Push(")");
base.Visit(node.Right);//解析右边
this._StringStack.Push(" " + node.NodeType.ToSqlOperator() + " ");
base.Visit(node.Left);//解析左边
this._StringStack.Push("(");
return node;
}
//变量表达式
protected override Expression VisitMember(MemberExpression node)
{
if (node == null) throw new ArgumentNullException("MemberExpression");
this._StringStack.Push(" [" + node.Member.Name + "] ");
return node;
}
//常量表达式
protected override Expression VisitConstant(ConstantExpression node)
{
if (node == null) throw new ArgumentNullException("ConstantExpression");
this._StringStack.Push( node.Value.ToString());
return node;
}
// 方法表达式
protected override Expression VisitMethodCall(MethodCallExpression m)
{
if (m == null) throw new ArgumentNullException("MethodCallExpression");
string format;
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:
throw new NotSupportedException(m.NodeType + " is not supported!");
}
this.Visit(m.Object);
this.Visit(m.Arguments[0]);
string right = this._StringStack.Pop();
string left = this._StringStack.Pop();
this._StringStack.Push(String.Format(format, left, right));
return m;
}
}
在遍历表达式目录树的时候顺便把SQL也拼装出来了,ORM就是在做这样的事情。Linq to SQL 的意义就是希望使用者不用关注SQL语句,可以直接从可读性很强的lambda表达式出发。本例中是通过栈来从右往左进行遍历。
这样一来,上述实现可以应对更加复杂的表达式目录树:
例子一:
{
//select * from People where Age>5 and Id>5
Expression<Func<People, bool>> lambda = x => x.Age > 5
&& x.Id > 5
&& x.Name.StartsWith("1")
&& x.Name.EndsWith("1")
&& x.Name.Contains("1");
string sql = string.Format("Delete From [{0}] WHERE {1}"
, typeof(People).Name
, " [Age]>5 AND [ID] >5"
);
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
}
例子二:
{
//且或非
Expression<Func<People, bool>> lambda = x => x.Age > 5 && x.Name == "A" || x.Id > 5;
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
}
{
Expression<Func<People, bool>> lambda = x => x.Age > 5 || (x.Name == "A" && x.Id > 5);
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
}
{
Expression<Func<People, bool>> lambda = x => (x.Age > 5 || x.Name == "A") && x.Id > 5;
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
}
表达式链接
Expression<Func<People, bool>> lambda1 = x => x.Age > 5;
Expression<Func<People, bool>> lambda2 = x => x.Id > 5;
Expression<Func<People, bool>> lambda3 = lambda1.And(lambda2);
Expression<Func<People, bool>> lambda4 = lambda1.Or(lambda2);
Expression<Func<People, bool>> lambda5 = lambda1.Not();
Do1(lambda3);
Do1(lambda4);
Do1(lambda5);
private static void Do1(Expression<Func<People, bool>> func)
{
List<People> people = new List<People>()
{
new People(){Id=4,Name="123",Age=4},
new People(){Id=5,Name="234",Age=5},
new People(){Id=6,Name="345",Age=6},
};
List<People> peopleList = people.Where(func.Compile()).ToList();
}
对于这三个操作,通常是进行扩展的:
- 合并表达式 expr1 AND expr2
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
//return expression.lambda<func<t, bool>>(expression.andalso(expr1.body, expr2.body), expr1.parameters);
//先判断非空 空不能visit
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<Func<T, bool>>(body, newParameter);
}
合并表达式 expr1 or expr2
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> 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<Func<T, bool>>(body, newParameter); }
表达式Not操作
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr)
{
var candidateExpr = expr.Parameters[0];
var body = Expression.Not(expr.Body);
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}