由浅入深表达式树(一)创建表达式树

  为什么要学习表达式树?表达式树是将我们原来可以直接由代码编写的逻辑以表达式的方式存储在树状的结构里,从而可以在运行时去解析这个树,然后执行,实现动态的编辑和执行代码。LINQ to SQL就是通过把表达式树翻译成SQL来实现的,所以了解表达树有助于我们更好的理解 LINQ to SQL,同时如果你有兴趣,可以用它创造出很多有意思的东西来。

  表达式树是随着.NET 3.5推出的,所以现在也不算什么新技术了。但是不知道多少人是对它理解的很透彻, 在上一篇Lambda表达式的回复中就看的出大家对Lambda表达式和表达式树还是比较感兴趣的,那我们就来好好的看一看这个造就了LINQ to SQL以及让LINQ to Everything的好东西吧。

  本系列计划三篇,第一篇主要介绍表达式树的创建方式。第二篇主要介绍表达式树的遍历问题。第三篇,将利用表达式树打造一个自己的LinqProvider。

  本文主要内容:

  • 由Lambda表达式创建简单的表达式树
  • 手动创建复杂的表达式树
  • 表达式树类型列表及示例

创建一个简单的Lambda表达式树

  在 上一篇Lambda表达式中我们提到了可以直接根据Lambda表达式来创建表达式树,这应该是最直接的创建表达式树的方式了。

Expression<Func<int, int>> expr = x => x + 1;
Console.WriteLine(expr.ToString());  // x=> (x + 1)

// 下面的代码编译不通过
Expression<Func<int, int, int>> expr2 = (x, y) => { return x + y; };
Expression<Action<int>> expr3 = x => {  };

  但是别想象的太美好,这种方式只能创建最简单的表达式树,复杂点的编译器就不认识了。

  右边是一个Lambda表达式,而左边是一个表达式树。为什么可以直接赋值呢?这个就要多亏我们的Expression<TDelegate>泛型类了。而Expression<TDelegate>是直接继承自LambdaExpression的,我们来看一下Expression的构造函数:

internal Expression(Expression body, string name, bool tailCall, ReadOnlyCollection<ParameterExpression> parameters)
    : base(typeof(TDelegate), name, body, tailCall, parameters)
{
}

  实际上这个构造函数什么也没有做,只是把相关的参数传给了父类,也就是LambdaExpression,由它把我们表达式的主体,名称,以及参数保存着。

Expression<Func<int, int>> expr = x => x + 1;
Console.WriteLine(expr.ToString());  // x=> (x + 1)

var lambdaExpr = expr as LambdaExpression;
Console.WriteLine(lambdaExpr.Body);   // (x + 1)
Console.WriteLine(lambdaExpr.ReturnType.ToString());  // System.Int32

foreach (var parameter in lambdaExpr.Parameters)
{
    Console.WriteLine("Name:{0}, Type:{1}, ",parameter.Name,parameter.Type.ToString());
}

//Name:x, Type:System.Int32

  简单的来说,Expression<TDelegate>泛型类做了一层封装,方便我们根据Lambda表达式来创建Lambda表达式树。它们中间有一个转换过程,而这个转换的过程就发生在我们编译的时候。还记得我们Lambda表达式中讲的么?Lambda表达式在编译之后是普通的方法,而Lambda式树是以一种树的结构被加载到我们的运行时的,只有这样我们才可以在运行时去遍历这个树。但是为什么我们不能根据Expression<TDelegate>来创建比较复杂的表达式树呢?您请接着往下看。

创建一个复杂的Lambda表达式树

  下面我们就来一步一步的创建一个复杂的表达式树,你们准备好了么?上面我们讲到直接由Lambda表达式的方式来创建表达式树,可惜只限于一种类型。下面我们就来演示一下如何创建一个无参无返回值的表达式树。

// 下面的方法编译不能过 
/*
Expression<Action> lambdaExpression2 = () =>
{
    for (int i = 1; i <= 10; i++)
    {
        Console.WriteLine("Hello");
    }
};
*/     
       
// 创建 loop表达式体来包含我们想要执行的代码
LoopExpression loop = Expression.Loop(
    Expression.Call(
        null,
        typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }),
        Expression.Constant("Hello"))
        );

// 创建一个代码块表达式包含我们上面创建的loop表达式
BlockExpression block = Expression.Block(loop);

// 将我们上面的代码块表达式
Expression<Action> lambdaExpression =  Expression.Lambda<Action>(block);
lambdaExpression.Compile().Invoke();

  上面我们通过手动编码的方式创建了一个无参的Action,执行了一组循环。代码很简单,重要的是我们要熟悉这些各种类型的表达式以及他们的使用方式。上面我们引入了以下类型的表达式:

  看起来神密的表达式树也不过如此嘛?如果大家去执行上面的代码,就会陷入死循环,我没有为loop加入break的条件。为了方便大家理解,我是真的一步一步来啊,现在我们就来终止这个循环。就像上面那一段不能编译通过的代码实现的功能一样,我们要输出10个”Hello”。

  上面我们先写了一个LoopExpression,然后把它传给了BlockExpresson,从而形成的的一块代码或者我们也可以说一个方法体。但是如果我们有多个执行块,而且这多个执行块里面需要处理同一个参数,我们就得在block里面声明这些参数了。

ParameterExpression number=Expression.Parameter(typeof(int),"number");
            
BlockExpression myBlock = Expression.Block(
    new[] { number },
    Expression.Assign(number, Expression.Constant(2)),
    Expression.AddAssign(number, Expression.Constant(6)),
    Expression.DivideAssign(number, Expression.Constant(2)));

Expression<Func<int>> myAction = Expression.Lambda<Func<int>>(myBlock);
Console.WriteLine(myAction.Compile()());
// 4

  我们声明了一个int的变量并赋值为2,然后加上6最后除以2。如果我们要用变量,就必须在block的你外面声明它,并且在block里面把它引入进来。否则在该表达式树时会出现,变量不在作用域里的错。

  下面我们继续我们未完成的工作,为循环加入退出条件。为了让大家快速的理解loop的退出机制,我们先来看一段伪代码:

LabelTarget labelBreak = Expression.Label();
Expression.Loop(
    "如果 条件 成功"
        "执行成功的代码"
    "否则"
        Expression.Break(labelBreak) //跳出循环
    , labelBreak); 

  我们需要借助于LabelTarget 以及Expression.Break来达到退出循环的目地。下面我们来看一下真实的代码:

LabelTarget labelBreak = Expression.Label();
ParameterExpression loopIndex = Expression.Parameter(typeof(int), "index");

BlockExpression block = Expression.Block(
new[] { loopIndex },
// 初始化loopIndex =1 
    Expression.Assign(loopIndex, Expression.Constant(1)),
    Expression.Loop(
        Expression.IfThenElse(
            // if 的判断逻辑
            Expression.LessThanOrEqual(loopIndex, Expression.Constant(10)),
            // 判断逻辑通过的代码
            Expression.Block(
                Expression.Call(
                    null,
                    typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }),
                    Expression.Constant("Hello")),
                Expression.PostIncrementAssign(loopIndex)),
            // 判断不通过的代码
            Expression.Break(labelBreak)
            ),labelBreak));

// 将我们上面的代码块表达式
Expression<Action> lambdaExpression =  Expression.Lambda<Action>(block);
lambdaExpression.Compile().Invoke();

  希望上面的代码没有阻止你学习表达式树的决心J 。

  好吧,我们又学了几个新的类型的表达式,来总结一下:

  到这里,我想大家应该对表达式树的构建有了一个清楚的认识。至于为什么不允许我们直接基于复杂的Lambda表达式来创建表达式树呢?

  • 这里的Lambda表达式实际上是一个Expression Body。
  • 这个Expression Body实际上就是我们上面讲到的Expression中的一种。
  • 也就是说编译器需要时间去分析你到底是哪一种?
  • 最简单的x=> x+1之类的也就是Func<TValue,TKey> 是很容易分析的。
  • 实际这里面允许的Expression Body只有BinaryExpression。

最后,我们来完整的看一下.NET都为我们提供了哪些类型的表达式(下面这些类都是继承自Expression)。

TypeBinaryExpression

TypeBinaryExpression typeBinaryExpression =
    Expression.TypeIs(
        Expression.Constant("spruce"),
        typeof(int));

Console.WriteLine(typeBinaryExpression.ToString());
// ("spruce" Is Int32)

IndexExpression

ParameterExpression arrayExpr = Expression.Parameter(typeof(int[]), "Array");

ParameterExpression indexExpr = Expression.Parameter(typeof(int), "Index");

ParameterExpression valueExpr = Expression.Parameter(typeof(int), "Value");

Expression arrayAccessExpr = Expression.ArrayAccess(
    arrayExpr,
    indexExpr
);

Expression<Func<int[], int, int, int>> lambdaExpr = Expression.Lambda<Func<int[], int, int, int>>(
        Expression.Assign(arrayAccessExpr, Expression.Add(arrayAccessExpr, valueExpr)),
        arrayExpr,
        indexExpr,
        valueExpr
    );

Console.WriteLine(arrayAccessExpr.ToString());
// Array[Index]

Console.WriteLine(lambdaExpr.ToString());
// (Array, Index, Value) => (Array[Index] = (Array[Index] + Value)) 

Console.WriteLine(lambdaExpr.Compile().Invoke(new int[] { 10, 20, 30 }, 0, 5));
// 15

NewExpression

NewExpression newDictionaryExpression =Expression.New(typeof(Dictionary<int, string>));
Console.WriteLine(newDictionaryExpression.ToString());
// new Dictionary`2()

InvocationExpression

Expression<Func<int, int, bool>> largeSumTest =
    (num1, num2) => (num1 + num2) > 1000;

InvocationExpression invocationExpression= Expression.Invoke(
    largeSumTest,
    Expression.Constant(539),
    Expression.Constant(281));

Console.WriteLine(invocationExpression.ToString());
// Invoke((num1, num2) => ((num1 + num2) > 1000),539,281)

  今天我们演示了如何通过代码的方式去创建表达式树,然后总结了一下.NET为我们提供的表达式类型。下一篇,我们将继续研究表达式树的遍历问题,敬请期待,如果对于表达式树有兴趣的同学欢迎持续关注~,

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏angularejs学习篇

ref和out的区别在c#中 总结

270
来自专栏aCloudDeveloper

经典排序之 冒泡排序

Author: bakari  Date: 2012.7.30 排序算法有很多种,每一种在不同的情况下都占有一席之地。关于排序算法我分“经典排序之”系列分别述之...

1939
来自专栏大内老A

为什么System.Attribute的GetHashCode方法需要如此设计?

昨天我在实现《通过扩展改善ASP.NET MVC的验证机制[使用篇]》的时候为了Attribute 的一个小问题后耗费了大半天的精力,虽然最终找到了问题的症结并...

17010
来自专栏增长技术

Swift基础---Integers

1212
来自专栏菩提树下的杨过

java学习:字符串比较“==”与“equals”的差异及与c#的区别

.net中,其字符串特有的驻留机制,保证了在同一进程中,相同字符序列的字符串,只有一个实例,这样能避免相同内容的字符串重复实例化,以减少性能开销。 先来回顾一下...

2398
来自专栏菩提树下的杨过

数据结构C#版笔记--堆栈(Stack)

堆栈(Stack)最明显的特征就是“先进后出”,本质上讲堆栈也是一种线性结构,符合线性结构的基本特点:即每个节点有且只有一个前驱节点和一个后续节点。 相对前面学...

2019
来自专栏菩提树下的杨过

数据结构C#版笔记--顺序表(SeqList)

线性结构(Linear Stucture)是数据结构(Data Structure)中最基本的结构,其特征用图形表示如下: ? 即:每个元素前面有且只有一个元...

1839
来自专栏恰同学骚年

.NET中那些所谓的新语法之三:系统预定义委托与Lambda表达式

开篇:在上一篇中,我们了解了匿名类、匿名方法与扩展方法等所谓的新语法,这一篇我们继续征程,看看系统预定义委托(Action/Func/Predicate)和超爱...

883
来自专栏vue

装箱和拆箱

 装箱和拆箱       1、装箱:值类型----->引用类型       2、拆箱:引用类型----->值类型       3、我们判断是否发生了拆箱或者装箱...

944
来自专栏Golang语言社区

Golang语言社区--结构体数据排序

大家好,我是Golang社区主编彬哥,这篇是给大家讲解关于复杂数据结构排序的。 结构体,数据排序 package main import ( ...

3293

扫码关注云+社区