表达式目录树(Expression)

一:什么是表达式树

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

先创建一个People的实体,下面会用到

/// <summary>
/// 实体类
/// </summary>
public class People
{
public int Age { get; set; }
public string Name { get; set; }
public int Id;
}

我们可以通过下面创建表达式目录树,我们称之为A种方式:

Expression<Func<People, bool>> lambda = x => x.Id.ToString().IndexOf("") >= ;

我们还可以使用Expression来动态创建,我们称之为B种方式:

var peopleParam = Expression.Parameter(typeof(People), "x");//创建一个x,类型为people
//得到x.Id
MemberExpression idParam = Expression.Field(peopleParam, "Id"); //得到ToString方法
MethodInfo toStringWay = typeof(int).GetMethod("ToString", new Type[] { }); //得到IndexOf的方法,然后new Type[]这个代表是得到参数为string的一个方法
MethodInfo indexOfWay = typeof(string).GetMethod("IndexOf", new Type[] { typeof(string) }); //通过下面方法得到x.Id.ToString()
MethodCallExpression tostringResult = Expression.Call(idParam, toStringWay, new Expression[] { }); //通过下面方法得到x.Id.ToString().IndexOf("5") ,MethodCallExpression继承于Expression
MethodCallExpression indexOfResult = Expression.Call(tostringResult, indexOfWay, new Expression[] { Expression.Constant("") }); //x.Id.ToString().IndexOf("5")>=0
var lambdaBody = Expression.GreaterThanOrEqual(indexOfResult, Expression.Constant()); //得到x => x.Id.ToString().IndexOf("5") >= 0,后面的一个参数指的是x,如果有多个则指定多个
Expression<Func<People,bool>> lambdaResult = Expression.Lambda<Func<People, bool>>(lambdaBody, new ParameterExpression[]
{ peopleParam }); //通过lambdaResult.Compile()得到Func<People,bool>这样的委托,然后Invoke是调用委托
bool result = lambdaResult.Compile().Invoke(new People() { Id = });

A种和B种得到的结果是一致的,只不过第一种是通过lambda匿名方法来构建,第二种是通过动态的Expression来构建。另外下面的原理也是一样的

//普通的Lambda表达式
Func<int,int,int> func = (x,y)=> x + y - ;
//表达式目录树的Lambda表达式声明方式
Expression<Func<int, int, int>> expression = (x, y) => x + y - ;
//表达式目录树的拼接方式实现
ParameterExpression parameterx = Expression.Parameter(typeof(int), "x");
ParameterExpression parametery = Expression.Parameter(typeof(int), "y");
ConstantExpression constantExpression = Expression.Constant(, typeof(int));
BinaryExpression binaryAdd = Expression.Add(parameterx, parametery);
BinaryExpression binarySubtract = Expression.Subtract(binaryAdd, constantExpression);
Expression<Func<int, int, int>> expressionMosaic = Expression.Lambda<Func<int, int, int>>(binarySubtract, new ParameterExpression[]
{
parameterx,
parametery
});
int ResultLambda = func(, );
int ResultExpression = expression.Compile()(, );
int ResultMosaic = expressionMosaic.Compile()(, );
Console.WriteLine($"func:{ResultLambda}");
Console.WriteLine($"expression:{ResultExpression}");
Console.WriteLine($"expressionMosaic:{ResultMosaic}");

下面举例说明以下Expression.Block

ParameterExpression varExpr = Expression.Variable(typeof(int), "x"); //add(int x);
var ex1 = Expression.Assign(varExpr, Expression.Constant()); //x = 1; var ex1 = x;
var ex2 = Expression.Add(ex1, Expression.Constant()); //var ex2 = ex1 + 5;//
var ex4 = Expression.Add(ex2, Expression.Constant()); //var ex4 = ex2 + 9; //
var ex5 = Expression.Add(ex4, Expression.Constant()); // var ex5 = ex4 + 8; //
BlockExpression blockExpr = Expression.Block(
new ParameterExpression[] { varExpr },
ex1,
ex2,
ex4,
ex5
);

该代码等效于,返回的结果都以最后一个Expression为主,则为ex5这个表达式

public int add(int x)
{
x = ;
var ex1 = x;
var ex2 = ex1 + ;//
var ex4 = ex2 + ; //
var ex5 = ex4 + ; //
return ex5; //
}

Expression.Block没有返回值

{
Expression A = Expression.Constant("第一大");
Expression B = Expression.Constant("第二大");
Expression ex = Expression.GreaterThan(Expression.Constant(), Expression.Constant()); var method = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
var AM = Expression.Call(method, A);
var BM = Expression.Call(method, B); var condition = Expression.IfThenElse(ex, AM, BM);
var blockExpr = Expression.Block(condition); //IfThenElse是没有返回值的 foreach (var expr in blockExpr.Expressions)
Console.WriteLine(expr.ToString()); var lambdaExpression = Expression.Lambda<Action>(blockExpr).Compile();
lambdaExpression();
}

下图是Expression的一些变量

表达式目录树(Expression)

二:表达式目录树与委托

Expression一般都是都是配合委托一起来使用的,比如和委托Action(没有返回值),Func(至少有一个返回参数,且最后一个值为返回参数),Action,Func既可以直接传入一个与之匹配的实体方法,又可以传入lambda表达式这种匿名类(这种是声明lambda表达式的一种快捷方式)。Expression,Action,Func关键词是在.net 3.5之后出现的。Expression<Func<>>是可以转成Func的(通过compile()这个方法转换)。反过来则不行。我们可以理解为Func<>经过定义后,就无法改变它了。而表达式树(Expression<Func<>>则是可以进行变更的。Lambda

使用lambda表达声明表达式目录树的时候注意不能有{},即:

Func<int, int, int> func = (m, n) => m * n + ;

上面这样是可以的。但是下面这样是不被允许的:

 Expression<Func<int, int, int>> exp1 = (m, n) =>
{
return m * n + ;
};//不能有语句体 只能是一行,不能有大括号

下面的例子来解析一下委托和表达式目录树

  #region PrivateMethod
private static void Do1(Func<People, bool> func)
{
List<People> people = new List<People>();
people.Where(func);
}
private static void Do1(Expression<Func<People, bool>> func)
{
List<People> people = new List<People>()
{
new People(){Id=,Name="",Age=},
new People(){Id=,Name="",Age=},
new People(){Id=,Name="",Age=},
}; List<People> peopleList = people.Where(func.Compile()).ToList();
} private static IQueryable<People> GetQueryable(Expression<Func<People, bool>> func)
{
List<People> people = new List<People>()
{
new People(){Id=,Name="",Age=},
new People(){Id=,Name="",Age=},
new People(){Id=,Name="",Age=},
}; return people.AsQueryable<People>().Where(func);
}
#endregion

然后调用的时候为如下:

 Expression<Func<People, bool>> lambda1 = x => x.Age > ;
Expression<Func<People, bool>> lambda2 = x => x.Id > ;
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);

三:使用Expression来进行不同对象的相同名字的属性映射

如果我们有一个新的对象和People属性基本上一致,如下:

/// <summary>
/// 实体类Target
/// PeopleDTO
/// </summary>
public class PeopleCopy
{ public int Age { get; set; }
public string Name { get; set; }
public int Id;
}

现在我们想要把People的中Age,Name,Id等赋值给PeopleCopy,第一种我们直接想到的是硬编码,然后如下:

People people = new People()
{
Id = ,
Name = "加菲猫",
Age =
};
//PeopleCopy copy = (PeopleCopy)people; //这种强制转换肯定是不行的 PeopleCopy peopleCopy = new PeopleCopy()
{
Id = people.Id,
Name = people.Name,
Age = people.Age
};

但是如果有多个类型转换,要写N次,然后不同用且费力,所以我们会想到通用的方法,比如使用:【反射】,【序列化反序列化】,【缓存+表达式目录】,【泛型+表达式目录】,【AutoMapper】,我们可以用这五种方法都小试一下!

1:反射完成对象属性映射

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ExpressionDemo.MappingExtend
{
public class ReflectionMapper
{
/// <summary>
/// 反射
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
/// <param name="tIn"></param>
/// <returns></returns>
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;
}
}
}

2:使用序列化和反序列化来完成对象属性映射:

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ExpressionDemo.MappingExtend
{
/// <summary>
/// 使用第三方序列化反序列化工具
///
/// 还有automapper
/// </summary>
public class SerializeMapper
{
/// <summary>
/// 序列化反序列化方式
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
public static TOut Trans<TIn, TOut>(TIn tIn)
{
return JsonConvert.DeserializeObject<TOut>(JsonConvert.SerializeObject(tIn));
}
}
}

3:缓存+表达式目录树

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks; namespace ExpressionDemo.MappingExtend
{
/// <summary>
/// 生成表达式目录树 缓存
/// </summary>
public class ExpressionMapper
{
/// <summary>
/// 字典缓存--hash分布
/// </summary>
private static Dictionary<string, object> _Dic = new Dictionary<string, object>(); /// <summary>
/// 字典缓存表达式树
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
/// <param name="tIn"></param>
/// <returns></returns>
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);
}
}
}

4:泛型+表达式目录树

 using System;
using System.Collections.Generic;
using System.Linq.Expressions; namespace ExpressionDemo.MappingExtend
{
/// <summary>
/// 生成表达式目录树 泛型缓存
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
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)); //p.Age
MemberBinding memberBinding = Expression.Bind(item, property); //Age=p.Age
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);
}
//new PeopleCopy() {Age = p.Age, Name = p.Name, Id = p.Id}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
//p => new PeopleCopy() {Age = p.Age, Name = p.Name, Id = p.Id}
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);
}
}
}

5:使用.netFramwork框架自带的AutoMapper,首先我们要nuget添加引用AutoMapper即可直接使用,具体代码为:

 using AutoMapper;

 namespace ExpressionDemo.MappingExtend
{
public class AutoMapperTest
{
public static TOut Trans<TIn, TOut>(TIn tIn)
{
return Mapper.Instance.Map<TOut>(tIn);
}
}
}

五种方法我们分别调用一下,然后测试一下性能,代码如下:

 {
People people = new People()
{
Id = ,
Name = "加菲猫",
Age =
};
//使用AutoMapper之前必须要初始化对应的关系
Mapper.Initialize(x => x.CreateMap<People, PeopleCopy>()); long common = ;
long generic = ;
long cache = ;
long reflection = ;
long serialize = ;
long autoMapper = ;
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = ; i < ; 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 = ; i < ; i++)
{
PeopleCopy peopleCopy = AutoMapperTest.Trans<People, PeopleCopy>(people);
}
watch.Stop();
autoMapper = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = ; i < ; i++)
{
PeopleCopy peopleCopy = ReflectionMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
reflection = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = ; i < ; i++)
{
PeopleCopy peopleCopy = SerializeMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
serialize = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = ; i < ; i++)
{
PeopleCopy peopleCopy = ExpressionMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
cache = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = ; i < ; 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");
Console.WriteLine($"automapper = { autoMapper} ms");
//性能比automapper还要高
}

运行结果如下:

表达式目录树(Expression)

通过结果发现:反射和序列化运用的时间最多,而我们惊奇的发现表达式目录树+泛型缓存比框架自带的AutoMapper时间还短!有木有感觉超级腻害~!

四:ORM与表达式树目录的关系

我们平常项目中经常用到EF,其实都是继承Queryable,然后我们使用的EF通常都会使用 var items = anserDo.GetAll().Where(x => x.OrganizationId == input.oid || input.oid == 0) ,where其实传的就是表达式目录树。那我们来一步一步解析EF底层实现的具体逻辑。

lambada表达式上面说了能使用Expression来动态拼接,当然它还有一个神奇的功能,能动态的解耦。Expression有个类ExpressionVisitor

表达式目录树(Expression)

这个类中的Visit(Expression node)是解读表达式的入口,然后能够神奇的区分参数和方法体,然后将表达式调度到此类中更专用的访问方法中,然后一层一层的解析下去,一直到最终的叶节点!

将表达式调度到此类中更专用的访问方法中:我们来举例说明:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks; namespace ExpressionDemo.Visitor
{
public class OperationsVisitor : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return this.Visit(expression);
} protected override Expression VisitBinary(BinaryExpression b)
{
if (b.NodeType == ExpressionType.Add)
{
Expression left = this.Visit(b.Left);
Expression right = this.Visit(b.Right);
return Expression.Subtract(left, right);
} return base.VisitBinary(b);
} protected override Expression VisitConstant(ConstantExpression node)
{
return base.VisitConstant(node);
}
}
}

下面调用:

 {
//修改表达式目录树
Expression<Func<int, int, int>> exp = (m, n) => m * n + ;
OperationsVisitor visitor = new OperationsVisitor();
Expression expNew = visitor.Modify(exp);
}

visit这个这个方法能够识别出来 m*n+2 是个二叉树,会通过下面的图然后一步一步的进行解析,如果遇到m*n 这会直接调用VisitBinary(BinaryExpression b)这个方法,如果遇到m或者n会调用VisitParameter(ParameterExpression node)这个方法,

如果遇到2常量则会调用VisitConstant(ConstantExpression node),这就是visit神奇的调度功能!

表达式目录树(Expression)

我们EF写的where等lambda表达式,就是通过ExpressionVisitor这个类来反解析的!之前没有学习过表达式目录树,以为ef本来就应该这样写,有没有和我一样认为的?

我们现在模拟写一个lambda转换sql的方法

 using ExpressionDemo.DBExtend;
using System;
using System.Collections.Generic;
using System.Linq.Expressions; namespace ExpressionDemo.Visitor
{
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;
} /// <summary>
/// 如果是二元表达式
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitMember(MemberExpression node)
{
if (node == null) throw new ArgumentNullException("MemberExpression");
this._StringStack.Push(" [" + node.Member.Name + "] ");
return node;
}
/// <summary>
/// 常量表达式
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitConstant(ConstantExpression node)
{
if (node == null) throw new ArgumentNullException("ConstantExpression");
this._StringStack.Push(" '" + node.Value + "' ");
return node;
}
/// <summary>
/// 方法表达式
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
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[]);
string right = this._StringStack.Pop();
string left = this._StringStack.Pop();
this._StringStack.Push(String.Format(format, left, right)); return m;
}
}
}
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks; namespace ExpressionDemo.DBExtend
{
internal static class SqlOperator
{
internal static string ToSqlOperator(this ExpressionType type)
{
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 "<>";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
case ExpressionType.LessThan:
return "<";
case ExpressionType.LessThanOrEqual:
return "<=";
case (ExpressionType.Equal):
return "=";
default:
throw new Exception("不支持该方法");
} }
}
}

然后调用的时候如下:

 {
//修改表达式目录树
Expression<Func<int, int, int>> exp = (m, n) => m * n + ;
OperationsVisitor visitor = new OperationsVisitor();
Expression expNew = visitor.Modify(exp);
} {
Expression<Func<People, bool>> lambda = x => x.Age > && x.Id >
&& x.Name.StartsWith("")
&& x.Name.EndsWith("")
&& x.Name.Contains(""); 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 > && x.Name == "A" || x.Id > ;
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
}
{
Expression<Func<People, bool>> lambda = x => x.Age > || (x.Name == "A" && x.Id > );
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
}
{
Expression<Func<People, bool>> lambda = x => (x.Age > || x.Name == "A") && x.Id > ;
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
}

目前Expression只支持ExpressionType的84种操作符Add, AndAlso等等,然后VisitMethodCall这个方法中表示lambda能解析出来的方法名字,如果需要可以自行修改会得到对应的sql语句的where条件!

上一篇:CSS如何让DIV的宽度随内容的变化


下一篇:JavaS:网页中的显示和隐藏