打造自己的LINQ Provider(上):Expression Tree揭秘

iqueryable:构建IQueryable提供程序系列 构建一个IQueryable提供程序 这是( 写的有关写LINQ查询提供程序的一系列文章,我已经用markdown格式化了内容,并将其放在一个地方以便于参考。 跟进文章,这是旧的源代码: 和示例数据库。 您可以从其获得最新版本。 立即下载

概述

在.NET Framework 3.5中提供了LINQ 支持后,LINQ就以其强大而优雅的编程方式赢得了开发人员的喜爱,而各种LINQ Provider更是满天飞,如LINQ to NHibernate、LINQ to Google等,大有“一切皆LINQ”的趋势。LINQ本身也提供了很好的扩展性,使得我们可以轻松的编写属于自己的LINQ Provider。
本文为打造自己的LINQ Provider系列文章第一篇,主要介绍表达式目录树(Expression Tree)的相关知识。

认识表达式目录树

究竟什么是表达式目录树(Expression Tree),它是一种抽象语法树或者说它是一种数据结构,通过解析表达式目录树,可以实现我们一些特定的功能(后面会说到),我们首先来看看如何构造出一个表达式目录树,最简单的方法莫过于使用Lambda表达式,看下面的代码:
Expression<Func<int, int, int>> expression = (a, b) => a * b + 2;
在我们将Lambda表达式指定给Expression<TDelegate>类型的变量(参数)时,编译器将会发出生成表达式目录树的指令,如上面这段代码中的Lambda表达式(a, b) => a * b + 2将创建一个表达式目录树,它表示的是一种数据结构,即我们把一行代码用数据结构的形式表示了出来,具体来说最终构造出来的表达式目录树形状如下图所示:
TerryLee_0160
这里每一个节点都表示一个表达式,可能是一个二元运算,也可能是一个常量或者参数等,如上图中的ParameterExpression就是一个参数表达式,ConstantExpression是一个常量表达式,BinaryExpression是一个二元表达式。我们也可以在Visual Studio中使用Expression Tree Visualizer来查看该表达式目录树:
TerryLee_0166
查看结果如下图所示:
TerryLee_0162
这里说一句,Expression Tree Visualizer可以从 MSDN Code Gallery上的LINQ Sample中得到。现在我们知道了表达式目录树的组成,来看看.NET Framework到底提供了哪些表达式?如下图所示:
TerryLee_0161
它们都继承于抽象的基类Expression,而泛型的Expression<TDelegate>则继承于LambdaExpression。在Expression类中提供了大量的工厂方法,这些方法负责创建以上各种表达式对象,如调用Add()方法将创建一个表示不进行溢出检查的算术加法运算的BinaryExpression对象,调用Lambda方法将创建一个表示lambda 表达式的LambdaExpression对象,具体提供的方法大家可以查阅MSDN。上面构造表达式目录树时我们使用了Lambda表达式,现在我们看一下如何通过这些表达式对象手工构造出一个表达式目录树,如下代码所示:
static void Main(string[] args)
{
    ParameterExpression paraLeft = Expression.Parameter(typeof(int), "a");
    ParameterExpression paraRight = Expression.Parameter(typeof(int), "b");
    BinaryExpression binaryLeft = Expression.Multiply(paraLeft, paraRight);
    ConstantExpression conRight = Expression.Constant(2, typeof(int));
    BinaryExpression binaryBody = Expression.Add(binaryLeft, conRight);
    LambdaExpression lambda = 
        Expression.Lambda<Func<int, int, int>>(binaryBody, paraLeft, paraRight);
    Console.WriteLine(lambda.ToString());
    Console.Read();
}
这里构造的表达式目录树,仍然如下图所示:
TerryLee_0160
运行这段代码,看看输出了什么:
TerryLee_0158  
可以看到,通过手工构造的方式,我们确实构造出了同前面一样的Lambda表达式。对于一个表达式目录树来说,它有几个比较重要的属性:
Body:指表达式的主体部分;
Parameters:指表达式的参数;
NodeType:指表达式的节点类型,如在上面的例子中,它的节点类型是Lambda;
Type:指表达式的静态类型,在上面的例子中,Type为Fun<int,int,int>。
在Expression Tree Visualizer中,我们可以看到表达式目录树的相关属性,如下图所示:
TerryLee_0163 

表达式目录树与委托

大家可能经常看到如下这样的语言,其中第一句是直接用Lambda表达式来初始化了Func委托,而第二句则使用Lambda表达式来构造了一个表达式目录树,它们之间的区别是什么呢?
static void Main(string[] args)
{
    Func<int, int, int> lambda = (a, b) => a + b * 2;
    Expression<Func<int, int, int>> expression = (a, b) => a + b * 2;
} 
其实看一下IL就很明显,其中第一句直接将Lambda表达式直接编译成了IL,如下代码所示:
.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  .maxstack  3
  .locals init ([0] class [System.Core]System.Func`3<int32,int32,int32> lambda)
  IL_0000:  nop
  IL_0001:  ldsfld     class [System.Core]System.Func`3<int32,int32,int32> 
                        TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
  IL_0006:  brtrue.s   IL_001b
  IL_0008:  ldnull
  IL_0009:  ldftn      int32 TerryLee.LinqToLiveSearch.Program::'<Main>b__0'(int32,
                                                                             int32)
  IL_000f:  newobj     instance void class [System.Core]System.Func`3<int32,int32,int32>::.ctor(object,
                                                                                                native int)
  IL_0014:  stsfld     class [System.Core]System.Func`3<int32,int32,int32> 
                    TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
  IL_0019:  br.s       IL_001b
  IL_001b:  ldsfld     class [System.Core]System.Func`3<int32,int32,int32> 
                    TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
  IL_0020:  stloc.0
  IL_0021:  ret
}
而第二句,由于告诉编译器是一个表达式目录树,所以编译器会分析该Lambda表达式,并生成表示该Lambda表达式的表达式目录树,即它与我们手工创建表达式目录树所生成的IL是一致的,如下代码所示,此处为了节省空间省略掉了部分代码:
.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  .maxstack  4
  .locals init ([0] class [System.Core]System.Linq.Expressions.Expression`1<
                class [System.Core]System.Func`3<int32,int32,int32>> expression,
           [1] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0000,
           [2] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0001,
           [3] class [System.Core]System.Linq.Expressions.ParameterExpression[] CS$0$0002)
  IL_0000:  nop
  IL_0001:  ldtoken    [mscorlib]System.Int32
  IL_0006:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(...)
  IL_000b:  ldstr      "a"
  IL_0010:  call       class [System.Core]System.Linq.Expressions.ParameterExpression 
                        [System.Core]System.Linq.Expressions.Expression::Parameter(
                        class [mscorlib]System.Type,
  IL_0038:  call    class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle()
  IL_003d:  call    class [System.Core]System.Linq.Expressions.ConstantExpression 
                    [System.Core]System.Linq.Expressions.Expression::Constant(object,
                    class [mscorlib]System.Type)
  IL_0042:  call    class [System.Core]System.Linq.Expressions.BinaryExpression 
                    [System.Core]System.Linq.Expressions.Expression::Multiply(class [System.Core]System.Linq.Expressions.Expression,
                    class [System.Core]System.Linq.Expressions.Expression)
  IL_0047:  call    class [System.Core]System.Linq.Expressions.BinaryExpression
                    [System.Core]System.Linq.Expressions.Expression::Add(class [System.Core]System.Linq.Expressions.Expression,
                    class [System.Core]System.Linq.Expressions.Expression)
  IL_004c:  ldc.i4.2
  IL_004d:  newarr     [System.Core]System.Linq.Expressions.ParameterExpression
}
现在相信大家都看明白了,这里讲解它们的区别主要是为了加深大家对于表达式目录树的区别。

执行表达式目录树

前面已经可以构造出一个表达式目录树了,现在看看如何去执行表达式目录树。我们需要调用Compile方法来创建一个可执行委托,并且调用该委托,如下面的代码:
static void Main(string[] args)
{
    ParameterExpression paraLeft = Expression.Parameter(typeof(int), "a");
    ParameterExpression paraRight = Expression.Parameter(typeof(int), "b");
    BinaryExpression binaryLeft = Expression.Multiply(paraLeft, paraRight);
    ConstantExpression conRight = Expression.Constant(2, typeof(int));
    BinaryExpression binaryBody = Expression.Add(binaryLeft, conRight);
    Expression<Func<int, int, int>> lambda = 
        Expression.Lambda<Func<int, int, int>>(binaryBody, paraLeft, paraRight);
    Func<int, int, int> myLambda = lambda.Compile();
    int result = myLambda(2, 3);
    Console.WriteLine("result:" + result.ToString());
    Console.Read();
}
运行后输出的结果:
TerryLee_0159
这里我们只要简单的调用Compile方法就可以了,事实上在.NET Framework中是调用了一个名为ExpressionCompiler的内部类来做表达式目录树的执行(注意此处的Compiler不等同于编译器的编译)。另外,只能执行表示Lambda表达式的表达式目录树,即LambdaExpression或者Expression<TDelegate>类型。如果表达式目录树不是表示Lambda表达式,需要调用Lambda方法创建一个新的表达式。如下面的代码:
static void Main(string[] args)
{
    BinaryExpression body = Expression.Add(
        Expression.Constant(2),
        Expression.Constant(3));
    Expression<Func<int>> expression = 
        Expression.Lambda<Func<int>>(body, null);
    Func<int> lambda = expression.Compile();
    Console.WriteLine(lambda());
}

访问与修改表达式目录树

在本文一开始我就说过, 通过解析表达式目录树,我们可以实现一些特定功能,既然要解析表达式目录树,对于表达式目录树的访问自然是不可避免的。在.NET Framework中,提供了一个抽象的表达式目录树访问类ExpressionVisitor,但它是一个internal的,我们不能直接访问。幸运的是,在MSDN中微软给出了 ExpressionVisitor类的实现,我们可以直接拿来使用。该类是一个抽象类,微软旨在让我们在集成ExpressionVisitor的基础上,实现自己的表达式目录树访问类。现在我们来看简单的表达式目录树:
static void Main(string[] args)
{
    Expression<Func<int, int, int>> lambda = (a, b) => a + b * 2;
    Console.WriteLine(lambda.ToString());
} 
输出后为:
TerryLee_0164
现在我们想要修改表达式目录树,让它表示的Lambda表达式为(a,b)=>(a - (b * 2)),这时就需要编写自己的表达式目录树访问器,如下代码所示:
public class OperationsVisitor : ExpressionVisitor
{
    public Expression Modify(Expression expression)
    {
        return Visit(expression);
    }
    protected override Expression VisitBinary(BinaryExpression b)
    {
        if (b.NodeType == ExpressionType.Add)
        {
            Expression left = this.Visit(b.Left);
            Expression right = this.Visit(b.Right);
            return Expression.Subtract(left,right);
        }
        return base.VisitBinary(b);
    }
}
使用表达式目录树访问器来修改表达式目录树,如下代码所示:
static void Main(string[] args)
{
    Expression<Func<int, int, int>> lambda = (a, b) => a + b * 2;
    var operationsVisitor = new OperationsVisitor();
    Expression modifyExpression = operationsVisitor.Modify(lambda);
    Console.WriteLine(modifyExpression.ToString());
}
运行后可以看到输出:
TerryLee_0165
似乎我们是修改表达式目录树,其实也不全对,我们只是修改表达式目录树的一个副本而已,因为表达式目录树是不可变的,我们不能直接修改表达式目录树,看看上面的OperationsVisitor类的实现大家就知道了,在修改过程中复制了表达式目录树的节点。

为什么需要表达式目录树

通过前面的介绍,相信大家对于表达式目录树已经有些了解了,还有一个很重要的问题,就是为什么需要表达式目录树?在本文开始时,就说过通过解析表达式目录树,可以实现我们一些特定的功能,就拿LINQ to SQL为例,看下面这幅图:
TerryLee_0167
当我们在C#语言中编写一个查询表达式时,它将返回一个IQueryable类型的值,在该类型中包含了两个很重要的属性Expression和Provider,如下面的代码:
TerryLee_0168
我们编写的查询表达式,将封装为一种抽象的数据结构,这个数据结构就是表达式目录树,当我们在使用上面返回的值时,编译器将会以该值所期望的方式进行翻译,这种方式就是由Expression和Provider来决定。可以看到,这样将会非常的灵活且具有良好的可扩展性,有了表达式目录树,可以自由的编写自己的Provider,去查询我们希望的数据源。经常说LINQ为访问各种不同的数据源提供了一种统一的编程方式,其奥秘就在这里。然而需要注意的是LINQ to Objects并不需要任何特定的LINQ Provider,因为它并不翻译为表达式目录树,后面会说到这一点。

总结

本文详细介绍了表达式目录树的相关知识,为我们编写自己的LINQ Provider打下一个基础,希望对于大家有所帮助。查看目前网上的各种lINQ Provider,请访问 万般皆LINQ。
LinqToTwitter:Twitter API的LINQ提供程序(C#Twitter库) LINQ to Twitter是用于微博客服务的开源第三方LINQ Provider(Twitter库)。 它使用标准的LINQ语法进行查询,并包括通过进行更改的方法调用。 请通过以下进行LINQ to Twitter Survey: 。 例 以下查询将搜索结果返回给人们在Twitter上发布有关LINQ的推文: var twitterCtx = new TwitterContext (...); var searchResponse = await ( from search 立即下载

相关推荐

树形结构记事本treepad

树形结构记事本treepad 树形结构记事本treepad

Linq递归初始化easyUI Tree数据(.NET MVC+easyUI+Linq)

Linq递归初始化easyUI Tree数据(.NET MVC+easyUI+Linq)

m0_37546766的博客 160

LINQ实例教程-用Linq对TreeView进行操作

LINQ 实例 教程 LINQ 实例 教程

打造自己的LinQProvider(四)

打造自己的LinqProvider 前言 取这个标题,是要终结一篇08年的文章:打造自己的LinQProvider(上)和 打造自己的LinQProvider中 相信很多想自己写LinQProvider的同学,都看过上面的这篇文章,可惜作者写完了上和中,迟迟没有下篇。于是我看到了如下的评论: LinqProvider简介 本篇假设读者对Expression Tree 和L...

weixin_30765577的博客 202

计算机网络之NAT的解释与实验配置

NAT 静态NAT 动态NAT easy_ip NAT服务器映射

爱学习的JackYang的博客 1917

三菱FX5U通信之CCLINK操作步骤

11. 连接 CC‐Link 用的端子排,连接 Ver.1.10 对应 CC‐Link 专用电缆。备注:1、以上对应的寄存器都是一一对应的,主站的点数可以比从站多,但是都需要一一对应起来。3.直接安装孔,用于直接安装的螺钉孔(2‐φ4.5,安装螺钉为 M4 螺钉)2.点阵 LED(显示模块中设置的站号和测试模式的内容)5.次段扩展连接器(连接扩展模块的扩展电缆的连接器)4.动作状态(显示 LED 显示模块的动作状态)10 电源连接器,用于连接电源电缆的连接器。1.扩展电缆(扩展时连接用的电缆)

ZXL622722的博客 6931

LINQ:构建 IQueryable Provider - 第三部分:局部变量引用

英文原文是发表在MSDN Blogs的系列文章之一,英文渣渣,翻译,请直接。第三部分?难道上篇文章还没有讲完吗?我不是做了一个可以翻译和执行SQL命令并且返回一个对象序列的提供程序了吗?确实如此,但是也仅仅如此而已。我写的那个提供程序的功能实在太弱,它只支持一种查询操作符与少量比较运算符。然而,真正的查询提供程序必须要提供更多的查询操作与更复杂的交互方式。我的查询提供程序甚至还不支持将数据投影为其他形式。

极客神殿 312

【时间序列预测算法】——Holt-Winters算法介绍及代码实现

背景 一阶二阶三阶指数平滑方法拟合效果均较好,通过结合数据自然属性,数据自身、趋势变化、季节性,提出了这三个角度的衡量量,可以说,理解起来更自然、更亲切。 公式 一阶: sts_tst​是t时刻对xtx_txt​的估计值,这里只有一个参数α,是数据本身的平滑属性的体现。 二阶: 引入了时间趋势变量btb_tbt​,优化了一下一阶的公式。同时还可以预测t+m时刻的值: 三阶: 又引入了季节性周期描述变量ctc_tct​,优化了一下二阶的公式。同时还可以预测t+m时刻的值: NOTE:ct

BlackValley的博客 1万+

Func转Expression的方法(C#)

这篇文章写完后,发现网上有大量关于Expresstion和Func的讨论,可以不看我的,看这几篇,是一样的,还更深入一些: http://fascinatedwithsoftware.com/blog/post/2012/01/10/More-on-Expression-vs-Func-with-Entity-Framework.aspx http://fascinatedwithsoft...

weixin_34146986的博客 707

视觉检测在激光打码字符检测上的检测案例

在机器视觉检测技术中,OCR识别是比较典型的应用,应用的场景也比较丰富,基本上在制造业,每个工厂或者印刷厂都会用到视觉OCR识别检测,OCR字符识别的原理很简单,通过图像分割后,对字符进行学习、命名。在工业相机拍摄图像后,软件进行处理,从而读取结果。 随着技术的发展,激光打标技术被更多的应用在生产过程中,由激光打标代替油墨进行字符印刷,速度更快、效果更好。 但是随之而来的是在视觉检测上带来的影响,目前市面上应用的字符检测设备大多是由集成商单独开发的软件,在检测油墨印刷时,图像上的字符没有断点,非常连贯,所需

视觉检测设备-视觉检测系统-单张检测设备-卷料印品检测 1463

QCM2290 PM-3 如何让mirco-usb支持关机充电

PM4125-3插入type-C触发开机进入关机充电的CC脚中断条件是否可以软件上屏蔽, 做到只通过VBUS中断即可触发进入关机充电模式,达到和PM4125-2的效果一样只要插入micro USB 就可以触发进入关机充电模式。硬件团队提议修改寄存器 SCHG_L_MISC_SYSOK_CFG 0x164A bit[4] 目前修改如下 — a/BOOT.XF.4.1/boot_images/QcomPkg/Library/PmicLib/target/Agatti_pmCocos/psi/pm_confi

墨染天玑的博客 443

Lambda表达式

Lambda表达式 “Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。 ​ Lambda 表达式可以理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但...

weixin_41924879的博客 1万+

【EDA Tools】Spyglass 检查 Verilog 和 SystemVerilog 混合语言及 Lint 检查

Spyglass可以用于检查混合设计中的语言互操作性和一致性问题。对于设计中包含多种硬件描述语言的情况,Spyglass 能够识别并解析其中的模块及其互连,并对其进行验证。例如,在包含 Verilog 和 VHDL 的混合设计中,Spyglass 会自动识别并解析 Verilog 和 VHDL 文件及其相互连接。通过运行 Spyglass Lint 功能,可以检查混合设计中的语言一致性问题,例如不兼容的数据类型、互操作性问题以及其他可能导致设计故障或不一致的问题。

记录学习历程 5080

Python实现无人机避障:人工势场法从公式到代码详解

人工势场法是机器人路径规划中的经典局部避障算法,通过构造目标引力场与障碍物斥力场,使无人机沿势场下降方向运动。它无需构建全局栅格地图,计算量极小,适合飞控等算力受限的边缘设备。在无人机的动态避障、编队飞行等工程场景中,人工势场法常与A*、RRT等全局规划器结合,以弥补其易陷入局部极小值、目标不可达等缺陷。本文从力场数学模型出发,推导引力和斥力公式,基于Python和NumPy实现完整的二维无人机避障仿真,并讨论参数整定方法、常见异常现象以及局部极小值与目标不可达问题的改进策略,为轻量级自主避障系统提供可落地

weixin_34348174的博客 345

173、NPU的编译器开发:开源社区贡献指南

本文分享了在NPU编译器开源社区贡献的经验心得。作者通过实际案例(如卷积维度错误、NaN输出等)阐述了NPU编译器开发的特殊性:硬件迭代快、边界条件复杂、厂商代码不可全信。提出有价值的贡献应聚焦硬件适配测试、性能剖析工具和反模式文档三类内容。强调用实测数据沟通、主动承担维护责任、保持代码通用性等社区协作技巧,并建议通过RFC提前沟通、附硬件实测数据等方式提高PR通过率。最后指出,NPU编译器开发是与聪明人共同填坑的过程,鼓励开发者提交"有诚意的代码"而非完美代码。

m0_50546716的博客 35

【CAN总线测试】——通讯相关诊断测试

本章主要介绍通讯相关诊断测试用例设计

LOVE135149的博客 1815

[转]打造自己的LINQ Provider(上):Expression Tree揭秘

概述 在.NET Framework 3.5中提供了LINQ 支持后,LINQ就以其强大而优雅的编程方式赢得了开发人员的喜爱,而各种LINQ Provider更是满天飞,如LINQ to NHibernate、LINQ to Google等,大有“一切皆LINQ”的趋势。LINQ本身也提供了很好的扩展性,使得我们可以轻松的编写属于自己的LINQ Provider。 本文为打造自己的LINQ P...

weixin_33842304的博客 134

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

(转)打造自己的LINQ Provider(上):Expression Tree揭秘

原文地址:http://www.cnblogs.com/Terrylee/archive/2008/08/01/custom-linq-provider-part-1-expression-tree.html 概述 在.NET Framework 3.5中提供了LINQ 支持后,LINQ就以其强大而优雅的编程方式赢得了开发人员的喜爱,而各种LINQ Provider更是满天飞,如L...

weixin_30846599的博客 110

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

打造自己的LINQ Provider(上):Expression Tree揭秘<转>

概述 在.NET Framework 3.5中提供了LINQ 支持后,LINQ就以其强大而优雅的编程方式赢得了开发人员的喜爱,而各种LINQ Provider更是满天飞,如LINQ to NHibernate、LINQ to Google等,大有“一切皆LINQ”的趋势。LINQ本身也提供了很好的扩展性,使得我们可以轻松的编写属于自己的LINQ Prov...

weixin_34259159的博客 111

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

上一篇: 技巧:在Silverlight应用程序中进行数据验证
下一篇: 谈谈Silverlight 2中的视觉状态管理 Part2
TerryLeeTech
博客等级 码龄18年 78粉丝 10原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值