猿教程 Logo

LINQ 表达式树

猿教程 上一节中您已经了解了表达式。 现在,让我们在这里了解表达式树。

表达式树只不过是以树状数据结构排列的表达式。 表达式树中的每个节点都是一个表达式。 例如,表达式树可以用于表示数学公式x <y,其中x,<和y将被表示为表达式并且以树状结构排列。

表达式树是lambda表达式的内存表示。 它保存查询的实际元素,而不是查询的结果。

表达式树使lambda表达式的结构透明和明确。 您可以与表达式树中的数据交互,就像使用任何其他数据结构一样。

例如,参考下面的isTeenAgerExpr表达式:

相关实例:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.age > 12 && s.age < 20;

编译器将上述表达式转换为以下表达式树:

相关实例:

Expression.Lambda<Func<Student, bool>>(
                Expression.AndAlso(
                    Expression.GreaterThan(Expression.Property(pe, "Age"), Expression.Constant(12, typeof(int))),
                    Expression.LessThan(Expression.Property(pe, "Age"), Expression.Constant(20, typeof(int)))),
                        new[] { pe });

您还可以手动构建表达式树。 让我们看看如何为下面的简单lambda表达式构建一个表达式树:

相关实例:

Func<Student, bool> isAdult = s => s.age >= 18;

这个Func类型委托将被视为如下方法:

相关实例:

public bool function(Student s)
{
  return s.Age > 18;
}

要创建表达式树,首先,创建一个参数表达式,其中Student是参数的类型,“s”是参数的名称,如下所示:

相关实例:

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");

现在,使用Expression.Property()创建s.Age表达式,其中s是参数,Age是Student的属性名称。 (Expression是一个抽象类,包含静态辅助方法来手动创建表达式树。)

相关实例:

MemberExpression me = Expression.Property(pe, "Age");

现在,为18创建一个常量表达式:

相关实例:

ConstantExpression constant = Expression.Constant(18, typeof(int));

到目前为止,我们已经为s.Age(成员表达式)和18(常量表达式)构建了表达式树。 我们现在需要检查一个成员表达式是否大于一个常量表达式。 为此,使用Expression.GreaterThanOrEqual()方法,并传递成员表达式和常量表达式作为参数:

相关实例:

BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);

因此,我们为lambda表达式主体构建了一个表达式树。我们现在需要连接参数和正文表达式。 使用Expression.Lambda(body,parameters数组)连接lambda表达式的body和parameter部分s => s.age> = 18:

相关实例:

var isAdultExprTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });

这样,您可以为带有lambda表达式的简单Func代理构建表达式树。

相关实例:

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");

MemberExpression me = Expression.Property(pe, "Age");

ConstantExpression constant = Expression.Constant(18, typeof(int));

BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);

var ExpressionTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });

Console.WriteLine("Expression Tree: {0}", ExpressionTree);
		
Console.WriteLine("Expression Tree Body: {0}", ExpressionTree.Body);
		
Console.WriteLine("Number of Parameters in Expression Tree: {0}", 
                                ExpressionTree.Parameters.Count);
		
Console.WriteLine("Parameters in Expression Tree: {0}", ExpressionTree.Parameters[0]);

VB.NET相关实例:

Dim pe As ParameterExpression = Expression.Parameter(GetType(Student), "s")

Dim mexp As MemberExpression = Expression.Property(pe, "Age")

Dim constant As ConstantExpression = Expression.Constant(18, GetType(Integer))

Dim body As BinaryExpression = Expression.GreaterThanOrEqual(mexp, constant)

Dim ExpressionTree As Expression(Of Func(Of Student, Boolean)) = 
    Expression.Lambda(Of Func(Of Student, Boolean))(body, New ParameterExpression() {pe})

Console.WriteLine("Expression Tree: {0}", ExpressionTree)

Console.WriteLine("Expression Tree Body: {0}", ExpressionTree.Body)
		
Console.WriteLine("Number of Parameters in Expression Tree: {0}", 
                                ExpressionTree.Parameters.Count)
		
Console.WriteLine("Parameters in Expression Tree: {0}", ExpressionTree.Parameters(0))

运行结果:

Expression Tree: s => (s.Age >= 18) 
Expression Tree Body: (s.Age >= 18)
Number of Parameters in Expression Tree: 1
Parameters in Expression Tree: s

下图说明了创建表达式树的整个过程:



为什么用表达式树?

我们在前面的章节中已经看到,分配给Func <T>的lambda表达式编译为可执行代码,而分配给Expression <TDelegate>类型的lambda表达式编译到表达式树中。

可执行代码在同一应用程序域中排斥,以通过内存中集合进行处理。 可枚举静态类包含用于实现IEnumerable接口的内存中集合的扩展方法。 List<T>,Dictionary<T>等.Enumerable类中的扩展方法接受Func类型委托的谓词参数。 例如,Where扩展方法接受Func <TSource,bool>谓词。 然后将其编译成IL(中间语言)以处理在同一AppDomain中的内存中集合。

下图显示了Enumerable类中的扩展方法在何处包含Func委托作为参数:


Func委托是一个原始可执行代码,所以如果你调试代码,你会发现Func委托将被表示为不透明代码。 您不能看到其参数,返回类型和正文:


Func委托是针对内存中的集合,因为它将在同一个AppDomain中处理,但是远程LINQ查询提供者(如LINQ-to-SQL,EntityFramework或其他提供LINQ功能的第三方产品)呢? 他们将如何解析已编译成原始可执行代码的lambda表达式,以了解参数,lambda表达式的返回类型和构建运行时查询以进一步处理? 答案是表达式树。

表达式<TDelegate>被编译为称为表达式树的数据结构。

如果你调试代码,Expression委托将表示如下所示:


现在,您可以看到正常委托和表达式之间的区别。 表达式树是透明的。 您可以从表达式检索参数,返回类型和正文表达式信息,如下所示:

相关实例:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.Age > 12 && s.Age < 20;

Console.WriteLine("Expression: {0}", isTeenAgerExpr );
        
Console.WriteLine("Expression Type: {0}", isTeenAgerExpr.NodeType);

var parameters = isTeenAgerExpr.Parameters;

foreach (var param in parameters)
{
    Console.WriteLine("Parameter Name: {0}", param.Name);
    Console.WriteLine("Parameter Type: {0}", param.Type.Name );
}
var bodyExpr = isTeenAgerExpr.Body as BinaryExpression;

Console.WriteLine("Left side of body expression: {0}", bodyExpr.Left);
Console.WriteLine("Binary Expression Type: {0}", bodyExpr.NodeType);
Console.WriteLine("Right side of body expression: {0}", bodyExpr.Right);
Console.WriteLine("Return Type: {0}", isTeenAgerExpr.ReturnType);

运行结果:

Expression: s => ((s.Age > 12) AndAlso (s.Age < 20))
Expression Type: Lambda
Parameter Name: s
Parameter Type: Student
Left side of body expression: (s.Age > 12)
Binary Expression Type: AndAlso
Right side of body expression: (s.Age < 20)
Return Type: System.Boolean

LINQ-to-SQL或Entity Framework的LINQ查询不在同一个应用程序域中执行。 例如,以下对Entity Framework的LINQ查询从不会在程序中实际执行:

相关实例:

var query = from s in dbContext.Students
            where s.Age >= 18
            select s;

它首先被翻译成SQL语句,然后在数据库服务器上执行。

在查询表达式中找到的代码必须转换为可以作为字符串发送到另一个进程的SQL查询。 对于LINQ to SQL或实体框架,该进程恰好是一个SQL服务器数据库。 将数据结构(如表达式树)转换为SQL比将原始IL或可执行代码转换为SQL要容易得多,因为正如你所看到的,很容易从表达式中检索信息。

表达式树被创建用于将诸如查询表达式的代码转换为可以传递到某个其他进程并在那里执行的字符串的任务。

可查询静态类包括接受Expression类型的谓词参数的扩展方法。 此谓词表达式将转换为表达式树,然后将作为数据结构传递到远程LINQ提供者,以便提供者可以从表达式树构建合适的查询并执行查询。



版权声明:本站所有教程均为本站原创或翻译,转载请注明出处,请尊重他人劳动果实。请记住本站地址:www.yuanjiaocheng.net (猿教程) 作者:卿文刚
本文标题: C#环境
本文地址:http://www.yuanjiaocheng.net/Linq/linq-expression-tree.html