【C#基础】拥抱Lambda(2):表达式树与LINQ
一、声明
曾经遇到一个这样的场景:
GetEntities(a => a.OrderKindCodeId == 16 && a.IsDeleted, this.DefaultContext) protected IEnumerable<TEntity> GetEntities(Expression<Func<TDto, bool>> expression, IRepositoryContext context)
我就很奇怪,明明a => a.OrderKindCodeId == 16 && a.IsDeleted是Lambda表达式,为什么入参是表示式树呢?一次偶然调试中发现,原来C#支持表达式树的声明像Lambda一样写(当然有一定局限)。
上述报错,是编译器无法推断弱类型是表达式树类型还是Lambda类型,需要指定类型:
Func<string, string, bool> lambda = (a,b) => a.StartsWith(b); Expression<Func<string, string, bool>> e = (a, b) => a.StartsWith(b); var expressionToLambda = e.Compile(); // 将表达式树转化成lambda表达式,故类型与e相同,但内容与e有区别 var typeIsEqual = expressionToLambda.GetType() == lambda.GetType(); // true bool isEqual = lambda.Equals(expressionToLambda); // false
不同的原因应该是.Compile()的锅,此处不研究。
换一种方法声明表达式树:
static Func<string, bool> buildlambda(Expression<Func<string, bool>> exp1, Expression<Func<string, bool>> exp2) { ParameterExpression parameter = Expression.Parameter(typeof(string)); Expression left = exp1.Body; Expression right = exp2.Body; BinaryExpression AndAlso = Expression.AndAlso(left, right); return Expression.Lambda<Func<string, bool>>(AndAlso, parameter).Compile(); }
看起来很正常,但运行时compile会报错:
System.InvalidOperationException:“从作用域“”引用了“System.String”类型的变量“a”,但该变量未定义”
运算过程出错,因为找不到参数——啥意思?上例的错误其实相当于这样:
Expression<Func<string, string, bool>> e = (a, b) => a.StartsWith(b); var e1 = Expression.Lambda(e.Body); bool c1 = e == e1; // false
e1 = Expression.Lambda(e.Body, e.Parameters); // e1是Expression<T>类型,指定了参数,让运算有个目标 var e2 = e1 as Expression; // e2是LambdaExpression类型 c1 = e == e1; // false,类型不同
二、存储结构
表达式树是有稳定性的,它由一个一个节点挂成一棵树,那怎么修改它?动一棵树,那必然是要遍历的,要递归。
问题又来了,构建一个表达式树如此费劲(不使用lambda表达式下)干嘛要用它——表达式树存在的意义?
三、意义与应用
3 - 1 为什么需要表达式树
表达式树的应用,最后还是要.Compile()成lambda表达式,那为什么不直接用lambda?
⑴ 调试可见方法如何实现
⑵ Lambda表达式能转换成表达式树,然后由其他代码处理,从而达到在不同执行环境中执行等价的行为。
Linq To SQL :将源语言(比如C#)生成一个表达式树,其结果作为一个中间格式,再将其转换成目标平台上的本地语言(比如SQL)。
那么这个中间格式直接传lambda表达式行不行?
测试了一下,数据库响应速度特别慢,可能还会发生超时。仅就这个原因,也必须用表达式树了:
看用时:
3 - 2 ExpressionVisitor
上述提及与数据库交互,那就涉及节点值问题(例如节点是一个函数的值,我们希望直接传函数结果而不是传函数),尽量处理得干净一些,这就需要修改(/重构)树,可以使用ExpressionVisitor类。
ExpressionVisitor应该是一个抽象类,程序员按需求继承重写。
这个类主要处理好Parameter和各个节点类型,入口函数应该是Visit(Expression exp),此处仅列出一部分:
public virtual Expression Visit(Expression exp) { if (exp == null) return exp; switch (exp.NodeType) { case ExpressionType.And: // + case ExpressionType.AndAlso: // 并且 case ExpressionType.LessThan: // 小于 case ExpressionType.GreaterThan: // 大于 case ExpressionType.Equal: // 等于 case ExpressionType.NotEqual: // 不等于 return this.VisitBinary((BinaryExpression)exp); case ExpressionType.Constant: // 常数 return this.VisitConstant((ConstantExpression)exp); case ExpressionType.Parameter: // 参数 return this.VisitParameter((ParameterExpression)exp); case ExpressionType.MemberAccess: //从字段或属性进行读取的运算,如 obj.SampleProperty return this.VisitMemberAccess((MemberExpression)exp); case ExpressionType.Call: // 方法调用 return this.VisitMethodCall((MethodCallExpression)exp); case ExpressionType.Lambda: // Lambda表达式 return this.VisitLambda((LambdaExpression)exp); default: throw new Exception(string.Format("Unhandled expression type: '{0}'", exp.NodeType)); } }
应用上例,改动一下:
ExpressionVisitorTest:
public class ExpressionVisitorTest { public ExpressionVisitorTest() { } public Expression Visit(Expression exp) { if (exp == null) return exp; switch (exp.NodeType) { case ExpressionType.Parameter: return VisitParameter((ParameterExpression)exp); case ExpressionType.NotEqual: return this.VisitBinary((BinaryExpression)exp); case ExpressionType.MemberAccess: return this.VisitMemberAccess((MemberExpression)exp); case ExpressionType.Constant: return this.VisitConstant((ConstantExpression)exp); default: return exp; } } protected virtual Expression VisitBinary(BinaryExpression b) { Expression left = this.Visit(b.Left); Expression right = this.Visit(b.Right); Expression conversion = this.Visit(b.Conversion); if (left != b.Left || right != b.Right || conversion != b.Conversion) { if (b.NodeType == ExpressionType.Coalesce && b.Conversion != null) return Expression.Coalesce(left, right, conversion as LambdaExpression); else return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method); } return b; } public Expression VisitMemberAccess(MemberExpression exp) { Expression body = Visit(exp.Expression); if (body != exp.Expression) return Expression.MakeMemberAccess(exp, exp.Member); return exp; } public Expression VisitParameter(ParameterExpression exp) { return Parameter; } protected virtual Expression VisitConstant(ConstantExpression c) { return c; } public ParameterExpression Parameter { get; set; } }
先看看要啥效果:
ExpressionVisitor visitor = new ExpressionVisitorInstance();
string testStr = true ? "0" : "123"; Expression<Func<string, bool>> aa = a => a != testStr; var bb = visitor.Visit(aa); isequal = bb.Equals(aa); // false
例如重写VisitMemberAccess方法:
public class ExpressionVisitorInstance : ExpressionVisitor { protected override Expression VisitMemberAccess(MemberExpression m) { Expression expr = base.VisitMemberAccess(m); return SimplyMemberAccess(expr); } private static Expression SimplyMemberAccess(Expression expr) { MemberExpression me = expr as MemberExpression; if (me == null) return expr; object target; if (me.Expression == null) target = null; else if (me.Expression is ConstantExpression) target = (me.Expression as ConstantExpression).Value; else return expr; return Expression.Constant(me.Member.GetValue(target)); } }
MemberExpression的属性:
MemberExpression |
CanReduce |
能否化简 |
Expression |
值表达式 |
|
Member |
Reflection.RtFieldInfo类型,可处理此处获得真实值 |
|
NodeType |
节点类型 |
|
Type |
值类型 |
处理反射的帮助类:
public static class ReflectionHelper { public static object GetValue(this MemberInfo member, object component) { if (component == null && !member.CanGetFromNull()) return null; if (member is PropertyInfo) return ((PropertyInfo)member).GetValue(component, null); if (member is FieldInfo) return ((FieldInfo)member).GetValue(component); MethodInfo method = member as MethodInfo; if (method != null && typeof(void) != method.ReturnType && method.GetParameters().Length == 0) { return method.Invoke(component, null); } return null; } private static bool CanGetFromNull(this MemberInfo member) { if (member is PropertyInfo) return ((PropertyInfo)member).GetGetMethod().IsStatic; else if (member is FieldInfo) return ((FieldInfo)member).IsStatic; else if (member is MethodBase) return ((MethodBase)member).IsStatic; else return false; } }
3 - 3 表达式树的其他作用:
不同类型数字相加,详见 http://mng.bz/9m8i:
public static T Add<T>(T a, T b) { ParameterExpression left = Expression.Parameter(typeof(T), "a"), right = Expression.Parameter(typeof(T), "b"); Expression Add = Expression.Add(left, right); Func<T, T, T> add = Expression.Lambda<Func<T, T, T>>(Add, left, right).Compile(); return add(a, b); }
但略感到尴尬的是,现在使用的.net 4.6.1已经能支持类型推断了,会推断一个“合适的类型”,也即意味着不用人工去强转类型了:
int d1 = 1; double d2 = 2.1; float d4 = 5.5f; long d5 = (long)10; var d3 = d1+ d2 + d5 + d4; // 存在从int到double的隐式转换
参考:
[0] 深入C# 第三版
[1] https://www.cnblogs.com/FlyEdward/archive/2010/12/06/Linq_ExpressionTree7.html
[2] https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/how-to-modify-expression-trees