C#中的表达式树

  本人之前从未接触过表达式树的概念,所以特意从网上找到两篇这方面的资料学习了下。本文为阅读笔记性质博客!

    表达式树是.NET 3.5之后引入的,它是一个强大灵活的工具(比如用在LINQ中构造动态查询)。

    先来看看Expression类的API接口:

using System.Collections.ObjectModel;

 

namespace System.Linq.Expressions

{

// Summary:

// Represents a strongly typed lambda expression as a data structure in the

// form of an expression tree. This class cannot be inherited.

//

// Type parameters:

// TDelegate:

// The type of the delegate that the System.Linq.Expressions.Expression<tdelegate>

// represents.

public sealed class Expression<tdelegate> : LambdaExpression

{

// Summary:

// Compiles the lambda expression described by the expression tree into executable

// code.

//

// Returns:

// A delegate of type TDelegate that represents the lambda expression described

// by the System.Linq.Expressions.Expression<tdelegate>.

public TDelegate Compile();

}

}

    表达式树的语法如下:

Expression<Func<type,returnType>> = (param) => lamdaexpresion;

    我们先来看一个简单例子:

Expression<Func<int, int, int>> expr = (x, y) => x+y;

    这就是一个表达式树了。使用Expression Tree Visualizer工具(直接调试模式下看也可以,只不过没这个直观)在调试模式下查看这个表达式树(就是一个对象),如下:

    可以看到表达式树主要由下面四部分组成:

1、Body 主体部分

2、Parameters 参数部分

3、NodeType 节点类型

4、Lambda表达式类型

    对于前面举的例子,主体部分即x+y,参数部分即(x,y)Lambda表达式类型是Func<Int32, Int32, Int32>。注意主体部分可以是表达式,但是不能包含语句,如下这样: 

Expression<Func<int, int, int>> expr = (x, y) => { return x+y; };

     会报编译错误"Lambada expression with state body cannot be converted to expression tree":即带有语句的Lambda表达式不能转换成表达式树。

    用前面的方法虽然可以创建表达式树,但是不够灵活,如果要灵活构建表达式树,可以像下面这样:

ParameterExpression exp1 = Expression.Parameter(typeof(int), "a");

ParameterExpression exp2 = Expression.Parameter(typeof(int), "b");

 

BinaryExpression exp = Expression.Multiply(exp1,exp2);

var lamExp = Expression.Lambda<Func<int, int, int>>(exp, new ParameterExpression[] { exp1, exp2 });

    exp1exp2即表达式树的参数,exp是表达式树的主体。如果我利用Reflector反编译Expression<Func<intintint>> expr = (x, y) => { return x+y; };得到下面的C#代码:

ParameterExpression CS$0$0000;

ParameterExpression CS$0$0001;

Expression<Func<int, int, int>> expr = Expression.Lambda<Func<int, int, int>>(Expression.Multiply(CS$0$0000 = Expression.Parameter(typeof(int), "x"), CS$0$0001 = Expression.Parameter(typeof(int), "y")), new ParameterExpression[] { CS$0$0000, CS$0$0001 });

    可以看到它基本和上面的手动构建代码一致。再来看一个简单的例子:

Expression<Func<Customer, bool>> filter =

cust => Equal(Property(cust,"Region"),"North");

    可以用下面的代码手动构建效果等同于上面的表达式树:

// declare a parameter of type Customer named cust

 

ParameterExpression custParam = Expression.Parameter(

 

typeof(Customer), "custParam");

 

// compare (equality) the Region property of the

 

// parameter against the string constant "North"

 

BinaryExpression body = Expression.Equal(

 

Expression.Property(custParam, "Region"),

 

Expression.Constant("North", typeof(string)));

 

// formalise this as a lambda

 

Expression<Func<Customer, bool>> filter =

 

Expression.Lambda<Func<Customer, bool>>(body, cust);

    然后我们可以通过表达式树的Compile方法将表达式树编译成Lambda表达式,如下:

Func<Customer, bool> filterFunc = filter.Compile();

      

    但是Compile调用过程涉及动态代码生成,所以出于性能考虑最好只调用一次,然后缓存起来。或者像下面这样在静态构造块中使用(也只会调用一次):

public static class Operator<T>

{

private static readonly Func<T, T, T> add;

public static T Add(T x, T y)

{

return add(x, y);

}

static Operator()

{

var x = Expression.Parameter(typeof(T), "x");

var y = Expression.Parameter(typeof(T), "y");

var body = Expression.Add(x, y);

add = Expression.Lambda<Func<T, T, T>>(

body, x, y).Compile();

}

}

      

    Expression类包含下面几类静态方法(.NET 3.5中):

Arithmetic: Add, AddChecked, Divide, Modulo, Multiply, MultiplyChecked, Negate, NegateChecked, Power,

Subtract, SubtractChecked, UnaryPlus

 

Creation: Bind, ElementInit, ListBind, ListInit, MemberBind, MemberInit, New, NewArrayBounds, NewArrayInit

 

Bitwise: And, ExclusiveOr, LeftShift (<<), Not, Or, RightShift (>>)

 

Logical: AndAlso (&&), Condition (? :), Equal, GreaterThan, GreaterThanOrEqual, LessThan,

LessThanOrEqual, NotEqual, OrElse (||), TypeIs

 

Member Access: ArrayIndex, ArrayLength, Call, Field, Property, PropertyOrField

 

Other: Convert, ConvertChecked, Coalesce (??), Constant, Invoke, Lambda, Parameter, TypeAs, Quote

    下面我们类似前面重载一个浅拷贝的例子(比使用反射开销小):

using System;

using System.Linq;

using System.Linq.Expressions;

using System.Reflection;

 

namespace ExpressionTreeLab

{

class Program

{

static void Main(string[] args)

{

var p = new Person()

{

Name = "jxq",

Age = 23

};

var shallowCopy = Operator<Person>.ShallowCopy(p);

shallowCopy.Name = "feichexia";

Console.WriteLine(shallowCopy.Name);

Console.WriteLine(p.Name);

 

Console.ReadKey();

}

 

public class Person

{

public string Name { get; set; }

public int Age { get; set; }

}

 

public static class Operator<T>

{

private static readonly Func<T, T> ShallowClone;

 

public static T ShallowCopy(T sourcObj)

{

return ShallowClone.Invoke(sourcObj);

}

 

static Operator()

{

var origParam = Expression.Parameter(typeof(T), "orig");

 

// for each read/write property on T, create a new binding

// (for the object initializer) that copies the original's value into the new object

var setProps = from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)

where prop.CanRead && prop.CanWrite

select (MemberBinding)Expression.Bind(prop, Expression.Property(origParam, prop));

 

var body = Expression.MemberInit( // object initializer

Expression.New(typeof(T)), // ctor

setProps // property assignments

);

 

ShallowClone = Expression.Lambda<Func<T, T>>(body, origParam).Compile();

}

}

}

}

    继续看Expression.AndAlso的使用,它可以用来替代类似下面这种多条件与的情况:

Func<Person, Person, bool> personEqual = (person1, person2) => person1.Name == person2.Name && person1.Age == person2.Age;

if(personEqual(p1, p2))

{

Console.WriteLine("两个对象所有属性值都相等!");

}

    代码如下:

using System;

using System.Linq;

using System.Linq.Expressions;

using System.Reflection;

 

namespace ExpressionTreeLab

{

class Program

{

static void Main(string[] args)

{

var p1 = new Person()

{

Name = "jxq",

Age = 23

};

var p2 = new Person()

{

Name = "jxq",

Age = 23

};

 

if (Operator<Person>.ObjectPropertyEqual(p1, p2))

{

Console.WriteLine("两个对象所有属性值都相等!");

}

 

Console.ReadKey();

}

 

public class Person

{

public string Name { get; set; }

public int Age { get; set; }

}

 

public static class Operator<T>

{

private static readonly Func<T, T, bool> PropsEqual;

 

public static bool ObjectPropertyEqual(T obj1, T obj2)

{

return PropsEqual.Invoke(obj1, obj2);

}

 

static Operator()

{

var x = Expression.Parameter(typeof(T), "x");

var y = Expression.Parameter(typeof(T), "y");

 

// 获取类型T上的可读Property

var readableProps = from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)

where prop.CanRead

select prop;

 

Expression combination = null;

foreach (var readableProp in readableProps)

{

var thisPropEqual = Expression.Equal(Expression.Property(x, readableProp),

Expression.Property(y, readableProp));

 

if(combination == null)

{

combination = thisPropEqual;

}

else

{

combination = Expression.AndAlso(combination, thisPropEqual);

}

}

 

if(combination == null) // 如果没有需要比较的东西,直接返回false

{

PropsEqual = (p1, p2) => false;

}

else

{

PropsEqual = Expression.Lambda<Func<T, T, bool>>(combination, x, y).Compile();

}

}

}

}

}

    .NET 4.0中扩展了一些Expression的静态方法,使得编写动态代码更容易:

Mutation: AddAssign, AddAssignChecked, AndAssign, Assign, DivideAssign, ExclusiveOrAssign, LeftShiftAssign, ModuloAssign, MultiplyAssign, MultiplyAssignChecked, OrAssign, PostDecrementAssign, PostIncrementAssign, PowerAssign, PreDecrementAssign, PreIncrementAssign, RightShiftAssign, SubtractAssign, SubtractAssignChecked

 

Arithmetic: Decrement, Default, Increment, OnesComplement

 

Member Access: ArrayAccess, Dynamic

 

Logical: ReferenceEqual, ReferenceNotEqual, TypeEqual

 

Flow: Block, Break, Continue, Empty, Goto, IfThen, IfThenElse, IfFalse, IfTrue, Label, Loop, Return, Switch, SwitchCase, Unbox, Variable

 

Exceptions: Catch, Rethrow, Throw

 

Debug: ClearDebugInfo, DebugInfo

    下面是一个利用表达式树编写动态代码的例子(循环打印09):

using System;

using System.Linq.Expressions;

 

namespace ExpressionTreeLab

{

class Program

{

static void Main(string[] args)

{

var exitFor = Expression.Label("exitFor"); // jump label

var x = Expression.Variable(typeof(int), "x");

var body =

Expression.Block(

new[] { x }, // declare scope variables

Expression.Assign(x, Expression.Constant(0, typeof(int))), // init

Expression.Loop(

Expression.IfThenElse(

Expression.GreaterThanOrEqual( // test for exit

x,

Expression.Constant(10, typeof(int))

),

Expression.Break(exitFor), // perform exit

Expression.Block( // perform code

Expression.Call(

typeof(Console), "WriteLine", null, x),

Expression.PostIncrementAssign(x)

)

), exitFor

) // Loop ends

);

 

var runtimeLoop = Expression.Lambda<Action>(body).Compile();

runtimeLoop();

 

Console.Read();

}

 

}

}

    另外WhereIn扩展实现如下,如果前面的例子都熟悉了的话,这个自然也很容易看懂了:

/// <summary>

/// 使之支持Sql in语法

/// </summary>

/// <typeparam name = "T"></typeparam>

/// <typeparam name = "TValue"></typeparam>

/// <param name = "query"></param>

/// <param name = "obj"></param>

/// <param name = "values"></param>

/// <returns></returns>

public static IQueryable<T> WhereIn<T, TValue>(this IQueryable<T> query, Expression<Func<T, TValue>> obj, IEnumerable<TValue> values)

{

return query.Where(BuildContainsExpression(obj, values));

}

 

private static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(

Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)

{

if (null == valueSelector)

{

throw new ArgumentNullException("valueSelector");

}

if (null == values)

{

throw new ArgumentNullException("values");

}

var p = valueSelector.Parameters.Single();

if (!values.Any()) return e => false;

 

var equals = values.Select(value => (Expression) Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof (TValue))));

var body = equals.Aggregate(Expression.Or);

return Expression.Lambda<Func<TElement, bool>>(body, p);

}

    调用方式如下:

db.Users.WhereIIn(u => u.Id, new int[] { 1, 2, 3 });

    关于使用表达式树构建LINQ动态查询,请参考Dynamic Linq Queries with Expression Trees

posted @ 2016-05-23 19:36  常想一二,不思八九  阅读(330)  评论(0)    收藏  举报