概要简述
表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等
每一个表达式都有节点类型NodeType
例如:二元操作、lambda、对象访问登都可以通过一个数据结构进行表示
Expression
Expression
是一个抽象类,不可进行实例化,但是可以通过Expression提供的静态方法进行对应类型的表达式创建,例如Expression.Parameter()
、Expression.Constant()
等,还可以将多个表达式合并,例如Expression.MakeBinary()
、Expression.MakeMemberAccess
等
基本上只要你写的代码都能用表达式进行表示,包括try
、catch
,表达式还能替代反射进行对象的创建(大量创建对象效率比反射高,不仅创建效率高于反射,消耗内存也比反射小)
,拼接sql(使用Visitor)
二元操作
二元操作是指操作符两边都有操作数,常见的二元操作符号有:+
、-
、*
、/
、%
和逻辑运算等
二元操作符都可以划分为left
、right
,其实是一个二叉树的结构
例如:(3 * 5 + 2 * 5) * 9 + x * 2
ConstantExpression c3 = Expression.Constant(3);
ConstantExpression c5 = Expression.Constant(5);
ConstantExpression c2 = Expression.Constant(2);
ConstantExpression c6 = Expression.Constant(9);
ParameterExpression cx = Expression.Variable(typeof(int), "x");
BinaryExpression c3xc5 = Expression.MakeBinary(ExpressionType.Multiply, c3, c5); // 3 * 5
BinaryExpression c2xc5 = Expression.MakeBinary(ExpressionType.Multiply, c2, c5); // 2 * 5
BinaryExpression p1plusp2 = Expression.MakeBinary(ExpressionType.Add, c3xc5, c2xc5); // 3 * 5 + 2 * 5
BinaryExpression p1plusp2x6 = Expression.MakeBinary(ExpressionType.Multiply, p1plusp2, c6); // (3 * 5 + 2 * 5) * 9
BinaryExpression cxxc2 = Expression.MakeBinary(ExpressionType.Multiply, cx, c2); // x * 2
BinaryExpression exp = Expression.MakeBinary(ExpressionType.Multiply, p1plusp2x6, cxxc2); // (3 * 5 + 2 * 5) * 9 + x * 2
运行结果如下:
lambda
表达式允许的lambda仅能运行一行代码,多由箭头函数进行表示,在lambda表达式中分成两部分:parameter
、body
,前者为参数,后者为方法体,举个例子:(int c, int d, int e) => c * d + d * e + 2 * d
ParameterExpression pc = Expression.Parameter(typeof(int), "c");
ParameterExpression pd = Expression.Parameter(typeof(int), "d");
ParameterExpression pe = Expression.Parameter(typeof(int), "e");
BinaryExpression pcxpd = Expression.MakeBinary(ExpressionType.Multiply, pc, pd); // c * d
BinaryExpression pdxpe = Expression.MakeBinary(ExpressionType.Multiply, pd, pe); // d * e
BinaryExpression pdx2 = Expression.MakeBinary(ExpressionType.Multiply, pd, Expression.Constant(2)); // d * 2
BinaryExpression body = Expression.MakeBinary(ExpressionType.Add, Expression.MakeBinary(ExpressionType.Add, pcxpd, pdxpe), pdx2); // c * d + d * e + d * 2
Expression<Func<int, int, int, int>> c = Expression.Lambda<Func<int, int, int, int>>(body, pc, pd, pe); // (c, d, e) => c * d + d * e + d * 2
执行结果如下:
访问对象属性
表达式允许访问某个对象实例的属性,利用反射可以很好的进行对象的一些操作,甚至能不经反射直接创建对象实例
简单的访问对象属性
Student student = new Student()
{
Id = 1,
Name = "ainuo5213",
};
Type type = student.GetType();
PropertyInfo prop = type.GetProperty("Id");
ParameterExpression parameter = Expression.Parameter(type, "student");
MemberExpression exp = Expression.MakeMemberAccess(parameter, prop);
运行结果如下
利用表达式创建对象
利用表达式创建对象,只需要使用Expression.New
,如果需要初始化某些值的话使用Expression.MemberInit
Type stuType = typeof(Student);
List<MemberAssignment> memberAssigns = new List<MemberAssignment>();
PropertyInfo targetProp = stuType.GetProperty("Id", BindingFlags.Public | BindingFlags.Instance); // 获取属性信息
ConstantExpression c2 = Expression.Constant(2);
var newObjExp = Expression.New(stuType); // new Student()
MemberAssignment memberBinding = Expression.Bind(targetProp, c2); // Id = 2,绑定目标属性赋值
memberAssigns.Add(memberBinding);
var obj = Expression.MemberInit(newObjExp, memberAssigns); // new Student(){ Id = 2 }
执行结果如下:
利用lambda创建真的对象
前面的知识已经有lambda表达式的expression
使用方法和对象创建的表达式方法,这里将两者结合起来,进行创建内存中的对象
ParameterExpression idExp = Expression.Parameter(typeof(int), "Id"); // 参数Id
ParameterExpression nameExp = Expression.Parameter(typeof(string), "Name"); // 参数Name
Type stuType = typeof(Student);
List<MemberAssignment> memberAssigns = new List<MemberAssignment>(); // 属性声明列表
PropertyInfo idPro = stuType.GetProperty("Id", BindingFlags.Public | BindingFlags.Instance); // 获取
Id属性
PropertyInfo nameProp = stuType.GetProperty("Name", BindingFlags.Public | BindingFlags.Instance); // 获取Name属性
var newObjExp = Expression.New(stuType);
MemberAssignment idBinding = Expression.Bind(idPro, idExp); // 绑定Id
MemberAssignment nameBinding = Expression.Bind(nameProp, nameExp); // 绑定Name
memberAssigns.Add(idBinding);
memberAssigns.Add(nameBinding);
var body = Expression.MemberInit(newObjExp, memberAssigns); // 初始化
Expression<Func<int, string, Student>> c = Expression.Lambda<Func<int, string, Student>>(body, idExp, nameExp);
Student st = c.Compile()(1, "111");
Console.WriteLine(c.ToString());
Console.WriteLine(st.Id);
Console.WriteLine(st.Name);
执行结果如下:
小工具:DTO转换
public static TTarget Map(TSource source)
{
if (source == null)
{
return null;
}
Type sourceType = typeof(TSource);
Type targetType = typeof(TTarget);
Expression parameterExpression = Expression.Parameter(sourceType, "p");
List<MemberBinding> memberBindingList = new List<MemberBinding>();
foreach (PropertyInfo sourceProp in sourceType.GetProperties())
{
PropertyInfo targetProp = targetType.GetProperty(sourceProp.Name, BindingFlags.Public | BindingFlags.Instance);
if (targetProp == null || sourceProp.PropertyType != targetProp.PropertyType || !targetProp.CanWrite)
{
continue;
}
MemberExpression property = Expression.Property(parameterExpression, sourceProp); // p.xxx
MemberAssignment memberBinding = Expression.Bind(targetProp, property); // xxx = p.xxx
memberBindingList.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(targetType), memberBindingList);
Func<TSource, TTarget> func = Expression.Lambda<Func<TSource, TTarget>>(memberInitExpression, parameterExpression as ParameterExpression).Compile();
return func(source);
}
ExpressionVisitor
ExpressionVisitor
和Expression
一样也是个抽象类,不能直接用new
,需要继承进行对应逻辑的处理。
ExpressionVisitor
采用访问者模式
对Expression
的每一个节点进行访问,开发者可通过重写基类方法进行对应逻辑的处理,例如简单的拼接where条件的sql
public class ConditionExpressionVisitor : ExpressionVisitor
{
private Stack<string> _sqlStack = new Stack<string>();
public ConditionExpressionVisitor()
{
this._sqlStack.Clear();
}
[return: NotNullIfNotNull("node")]
protected override Expression VisitBinary(BinaryExpression node)
{
this._sqlStack.Push(")");
base.Visit(node.Right);
this._sqlStack.Push(" " + ExpressionUtil.ConditionOperator(node.NodeType) + " ");
base.Visit(node.Left);
this._sqlStack.Push("(");
return node;
}
public string GetSql()
{
return string.Concat(this._sqlStack.ToArray());
}
protected override Expression VisitMember(MemberExpression node)
{
base.Visit(node.Expression);
this._sqlStack.Push(" [" + node.Member.Name + "] ");
return node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
string op = string.Empty;
if (!this.IsNumber(node.Type))
{
op = "'";
}
this._sqlStack.Push($" {op}" + node.Value + $"{op}");
return base.VisitConstant(node);
}
private bool IsNumber(Type type)
{
return type == typeof(decimal) || type == typeof(double) || type == typeof(int) || type == typeof(float) || type == typeof(Enum);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
string _format = string.Empty;
switch (node.Method.Name)
{
case "StartsWith":
_format = "{0} LIKE {1}+'%'";
break;
case "EndsWith":
_format = "{0} LIKE '%'+{1}";
break;
case "Contains":
_format = "{0} LIKE '%'+{1}+'%'";
break;
default:
throw new NotSupportedException(node.NodeType + " is not supported!");
}
base.Visit(node.Object);
base.Visit(node.Arguments[0]);
string right = this._sqlStack.Pop();
string left = this._sqlStack.Pop();
this._sqlStack.Push(string.Format(_format, left, right));
return node;
}
}
执行结果如下:
ConditionExpressionVisitor operatorExpressionVisitor = new ConditionExpressionVisitor();
Expression<Func<Student, bool>> exp = n => n.Id > 50 && n.Name.StartsWith("张三") && n.Name == "111";
operatorExpressionVisitor.Visit(exp);
string sql = operatorExpressionVisitor.GetSql();
总的来说,expression可以表示你所写的任意代码,包括但不仅仅只有笔者所写的这几类,能做的事情很多(微软官方对于这部分描述较少,且没有例子),笔者代码仅供参考