一、声明

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

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. CentOS 集群初始化设置

    0. 前置操作 centos-7.9.2009-isos-x86_64安装包下载_开源镜像站-阿里云 下载CentOS-7-x86_64-DVD-2009.iso即可 1. 配置静态网络 1.1 查看 ...

  2. Python 潮流周刊#83:uv 的使用技巧(摘要)

    本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...

  3. uniapp请求封装-token无感刷新

    当前是vue3+ts版本的封装 vue3+js版本请求封装可参考 https://www.cnblogs.com/lovejielive/p/14343619.html token无感刷新,可自行删除 ...

  4. 编译Ubuntu 24.04 LTS 内核(BuildYourOwnKernel)

    1.配置环境 修改apt源 修改 /etc/apt/sources.list.d/ubuntu.sources ,添加 "deb-src"到 Types:,修改后的文件内容如下: ...

  5. 一步一步abp电商模块-1、搭建模块环境

    前言 目前在开发abp电商模块,打算做一步,写一步,算是对自己的记录,主要是参考nopcommoner 并结合abp模块开发 知识都是连贯的,如果你熟悉asp.net core 3.x.abp(非vN ...

  6. spring-springMVC-总结列表

    Spring 的优良特性 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API 控制反转:IOC--Inversion of Control,指的是将对象的创建权交给 Spri ...

  7. weixueyuan-Nginx HTTP模块3

    https://www.weixueyuan.net/nginx/http/ Nginx镜像模块:ngx_http_mirror_module Nginx 中镜像模块的功能是将用户的访问请求镜像复制到 ...

  8. runoob-NumPy(python)

    https://www.runoob.com/numpy/numpy-tutorial.html NumPy 教程 NumPy(Numerical Python) 是 Python 语言的一个扩展程序 ...

  9. DVWA靶场Authorisation Bypass (未授权绕过) 漏洞通关教程及源码审计

    Authorisation Bypass 授权绕过(Authorisation Bypass)是一种严重的安全,通过利用系统的或错误配置,绕过正常的访问控制机制,获得未经授权的访问权限.这种可能导致敏 ...

  10. RSA的原理和简单实践

    RSA加密是一种非对称加密,原理是: 使⽤算法可以⽣成两把钥匙 A 和 B 使⽤ A 加密的信息,使⽤ B 可以解开 使⽤ B 加密的信息,使⽤ A 可以解开 ⽇常使⽤中,我们把⼀把作为公钥公开发布. ...