一、声明

  曾经遇到一个这样的场景:

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

  (下载ExpressionVisitor.cs

  例如重写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的更多相关文章

  1. 表达式树在LINQ动态查询

    动态构建表达式树,最佳实践版,很实用! public class FilterCollection : Collection<IList<Filter>> { public F ...

  2. c#——表达式树在LINQ动态查询

    一般如果逻辑比较简单,只是存在有的情况多一个查询条件,有的情况不需要添加该查询条件 简单方式这样操作就可以了 public IQueryable<FileImport> DynamicCh ...

  3. C#中的Lambda表达式和表达式树

    在C# 2.0中,通过方法组转换和匿名方法,使委托的实现得到了极大的简化.但是,匿名方法仍然有些臃肿,而且当代码中充满了匿名方法的时候,可读性可能就会受到影响.C# 3.0中出现的Lambda表达式在 ...

  4. Lambda表达式和表达式树

    在C# 2.0中,通过方法组转换和匿名方法,使委托的实现得到了极大的简化.但是,匿名方法仍然有些臃肿,而且当代码中充满了匿名方法的时候,可读性可能就会受到影响.C# 3.0中出现的Lambda表达式在 ...

  5. Lambda表达式和Lambda表达式树

    LINQ的基本功能就是创建操作管道,以及这些操作需要的任何状态. 为了富有效率的使用数据库和其他查询引擎,我们需要一种不同的方式表示管道中的各个操作.即把代码当作可在编程中进行检查的数据. Lambd ...

  6. 深入学习C#匿名函数、委托、Lambda表达式、表达式树类型——Expression tree types

    匿名函数 匿名函数(Anonymous Function)是表示“内联”方法定义的表达式.匿名函数本身及其内部没有值或者类型,但是可以转换为兼容的委托或者表达式树类型(了解详情).匿名函数转换的计算取 ...

  7. 16.C#初见Lambda表达式及表达式树(九章9.1-9.3)

    在说明Lambda相关知识前,我们需要了解Lambda表达式常用于LINQ,那么我们来聊下LINQ. LINQ的基本功能就是创建操作管道,以及这些操作需要的任何状态.这些操作表示了各种关于数据的逻辑: ...

  8. C#复习笔记(4)--C#3:革新写代码的方式(Lambda表达式和表达式树)

    Lambda表达式和表达式树 先放一张委托转换的进化图 看一看到lambda简化了委托的使用. lambda可以隐式的转换成委托或者表达式树.转换成委托的话如下面的代码: Func<string ...

  9. C# 表达式树 创建、生成、使用、lambda转成表达式树~表达式树的知识详解

    笔者最近学了表达式树这一部分内容,为了加深理解,写文章巩固知识,如有错误,请评论指出~ 表达式树的概念 表达式树的创建有 Lambda法 和 组装法. 学习表达式树需要 委托.Lambda.Func& ...

  10. 转载:C#特性-表达式树

    原文地址:http://www.cnblogs.com/tianfan/ 表达式树基础 刚接触LINQ的人往往觉得表达式树很不容易理解.通过这篇文章我希望大家看到它其实并不像想象中那么难.您只要有普通 ...

随机推荐

  1. Sublime Text 4143 激活码

    1 .Windows激活方法 安装地址:Download - Sublime Text 使用浏览器打开hexed.it(https://hexed.it/) 点击"打开文件",选择 ...

  2. docker-compose安装mongo

    创建目录 [root@localhost tools]# mkdir -p /root/tools/mongo/{data,conf,init} 创建初始化用户脚本 [root@localhost m ...

  3. centOS7安装nginx及nginx配置

    安装所需插件1.安装gccgcc是linux下的编译器在此不多做解释,感兴趣的小伙伴可以去查一下相关资料,它可以编译 C,C++,Ada,Object C和Java等语言 命令:查看gcc版本 gcc ...

  4. Qt音视频开发29-Onvif云台控制

    一.前言 云台控制也是onvif功能中最常用的,最常用的功能排第一的是拿到视频流地址,排第二的就是云台控制了,云台控制的含义就是对带云台的摄像机进行上下左右的移动,一般云台摄像机都是带有一个小电机,一 ...

  5. 基于源码分析 SHOW GLOBAL STATUS 的实现原理

    问题 在 MySQL 中,查询全局状态变量的方式一般有两种:SHOW GLOBAL STATUS和performance_schema.global_status. 但不知道大家注意到没有,perfo ...

  6. 记录socket的使用

    今天记录一下socket的基本使用方法,直接上代码 initWebSocket() { //初始化weosocket const wsuri = "socket地址";//地址以w ...

  7. 字体查看比较工具 -- (采用wpf开发)

    为了进一步加深对字体文件的理解,我写了这个小工具.可以查看字体文件信息.显示字体文件包含的字体.可以从字体文件中抽取字体,保存为其子集. 加入qq群:565438497,下载最新程序. 1 显示字体文 ...

  8. CDS标准视图:付款锁定原因描述 I_PaymentBlockingReasonText

    视图名称:付款锁定原因描述 I_PaymentBlockingReasonText 视图类型:基础 视图代码: 点击查看代码 //Documentation about annotations can ...

  9. JVM虚拟机---常用JVM配置参数

    常用JVM配置参数 常用JVM配置参数主要有:Trace跟踪参数.堆的分配参数.栈的分配参数. 一.Trace跟踪参数 跟踪参数用于跟踪监控JVM,对于开发人员来讲用于JVM调优以及故障排查的. 1. ...

  10. Kotlin:【泛型】