【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
【C#语法糖 Lambda】2 - 表达式树与LINQ的更多相关文章
- 表达式树在LINQ动态查询
动态构建表达式树,最佳实践版,很实用! public class FilterCollection : Collection<IList<Filter>> { public F ...
- c#——表达式树在LINQ动态查询
一般如果逻辑比较简单,只是存在有的情况多一个查询条件,有的情况不需要添加该查询条件 简单方式这样操作就可以了 public IQueryable<FileImport> DynamicCh ...
- C#中的Lambda表达式和表达式树
在C# 2.0中,通过方法组转换和匿名方法,使委托的实现得到了极大的简化.但是,匿名方法仍然有些臃肿,而且当代码中充满了匿名方法的时候,可读性可能就会受到影响.C# 3.0中出现的Lambda表达式在 ...
- Lambda表达式和表达式树
在C# 2.0中,通过方法组转换和匿名方法,使委托的实现得到了极大的简化.但是,匿名方法仍然有些臃肿,而且当代码中充满了匿名方法的时候,可读性可能就会受到影响.C# 3.0中出现的Lambda表达式在 ...
- Lambda表达式和Lambda表达式树
LINQ的基本功能就是创建操作管道,以及这些操作需要的任何状态. 为了富有效率的使用数据库和其他查询引擎,我们需要一种不同的方式表示管道中的各个操作.即把代码当作可在编程中进行检查的数据. Lambd ...
- 深入学习C#匿名函数、委托、Lambda表达式、表达式树类型——Expression tree types
匿名函数 匿名函数(Anonymous Function)是表示“内联”方法定义的表达式.匿名函数本身及其内部没有值或者类型,但是可以转换为兼容的委托或者表达式树类型(了解详情).匿名函数转换的计算取 ...
- 16.C#初见Lambda表达式及表达式树(九章9.1-9.3)
在说明Lambda相关知识前,我们需要了解Lambda表达式常用于LINQ,那么我们来聊下LINQ. LINQ的基本功能就是创建操作管道,以及这些操作需要的任何状态.这些操作表示了各种关于数据的逻辑: ...
- C#复习笔记(4)--C#3:革新写代码的方式(Lambda表达式和表达式树)
Lambda表达式和表达式树 先放一张委托转换的进化图 看一看到lambda简化了委托的使用. lambda可以隐式的转换成委托或者表达式树.转换成委托的话如下面的代码: Func<string ...
- C# 表达式树 创建、生成、使用、lambda转成表达式树~表达式树的知识详解
笔者最近学了表达式树这一部分内容,为了加深理解,写文章巩固知识,如有错误,请评论指出~ 表达式树的概念 表达式树的创建有 Lambda法 和 组装法. 学习表达式树需要 委托.Lambda.Func& ...
- 转载:C#特性-表达式树
原文地址:http://www.cnblogs.com/tianfan/ 表达式树基础 刚接触LINQ的人往往觉得表达式树很不容易理解.通过这篇文章我希望大家看到它其实并不像想象中那么难.您只要有普通 ...
随机推荐
- 2023 IDC中国数字金融论坛丨中电金信向行业分享“源启+应用重构”新范式
9月8日,IDC主办的"2023 IDC中国数字金融论坛"在北京召开.中电金信受邀参会,并带来了深度数字化转型趋势之下关于应用重构的分享与洞见. 论坛重点关注金融科技创新发展趋势与 ...
- 解锁 Git Log 更多实用技巧
目前,在软件开发的协作中,Git 无疑是版本控制的王者. 而其中的 git log 命令,犹如一把强大的历史探寻之剑,能够帮助我们深入洞察项目的演进历程. 本篇将为大家整理解读几个实用的 git Lo ...
- 【WEB前端】【报错解决】This request has been blocked; the content must be served over HTTPS.
问题描述 部署WEB项目后,开启了强制HTTPS,产生如下错误: Mixed Content: The page at 'https://ask.mllt.vip/index.php/data1.ht ...
- 有邻App覆盖3000多个小区成杭州用户量最大的邻里分享经济平台 杨仁斌:开创新社区时代
[浙商创业青云榜] 当下中国大多数的城市社区里,邻居这个词是个淡薄的概念. 2014年,一名阿里高管决心改变现状,辞职创业,深挖社区分享经济,准备用一款手机App"有邻",去敲开陌 ...
- 智谱开源CogAgent的最新模型CogAgent-9B-20241220,全面领先所有开闭源GUI Agent模型
在现代数字世界中,图形用户界面(GUI)是人机交互的核心.然而,尽管大型语言模型(LLM)如ChatGPT在处理文本任务上表现出色,但在理解和操作GUI方面仍面临挑战,因此最近一年来,在学界和大模型社 ...
- Qt音视频开发23-通用视频控件
一.前言 在之前做的视频监控系统中,根据不同的用户需要,做了好多种视频监控内核,有ffmpeg内核的,有vlc内核的,有mpv内核的,还有海康sdk内核的,为了做成通用的功能,不同内核很方便的切换,比 ...
- 特斯拉CEO埃隆马.斯克的五步工作法,怎么提高工程效率加速产品开发?
简介 在<埃隆·马斯克传>这本书中,有两个章节写到了特斯拉 CEO 埃隆马斯克为了在一段时间内,提升特斯拉汽车 model 3 的产能到每个月 5000 辆这个数量级,在书中叫 " ...
- 第十三章 HashMap&HashSet源码解析
HashMap源码解析 5.1.对于HashMap需要掌握以下几点 Map的创建:HashMap() 往Map中添加键值对:即put(Object key, Object value)方法 获取Map ...
- biancheng-HBase
目录http://c.biancheng.net/view/6509.html 1HBase是什么?2HBase的优势有哪些?3Hadoop与HBase的关系4HDFS5HDFS的特点与使用场景6HB ...
- ZUC-S盒输入输出测试
问题 实现以二进制.十进制.十六进制的形式输入,经过S盒,输出十六进制 输入: 1.二进制:10001010010011110000011110111101 2.十进制:2320435133 3.十六 ...