Overview
With LINQ support in the. NET Framework 3.5, LINQ won the developer's love for its powerful and elegant programming, and various LINQ provider, such as LINQ to NHibernate, LINQ to Google, etc. There is a tendency to "Everything is LINQ". LINQ itself also provides good extensibility, making it easy for us to write our own LINQ Provider.
This article is about creating your own LINQ Provider series article, which focuses on the knowledge of expression trees.
Recognize an expression tree
What exactly is an expression tree, an abstract syntax tree or a data structure that can implement some of our specific functions by parsing an expression tree, we'll start by looking at how to construct a tree of expressions, The simplest way to do this is to use a lambda expression to see the following code:
expression<func<int int int.2;
When we assign a lambda expression to a variable (parameter) of type expression<tdelegate>, the compiler emits instructions to build the expression tree, such as the lambda expression in the preceding code (a, B) and A * b + 2 will create an expression tree that represents a data structure, that is, we represent a line of code in the form of a data structure, specifically the resulting expression tree shape as shown:
Each node here represents an expression, possibly a two-dollar operation, or a constant or parameter, such as parameterexpression in a parameter expression, constantexpression is a constant expression, Binaryexpression is a two-dollar expression. We can also use expression tree Visualizer in Visual Studio to view the expression trees:
View the results as shown:
In this case, Expression Tree Visualizer can be obtained from the LINQ sample on MSDN Code Gallery. Now that we know the composition of the expression tree, let's see what expressions the. NET Framework provides. As shown in the following:
They all inherit from the abstract base class expression, while the generic expression<tdelegate> inherit from the LambdaExpression. A number of factory methods are provided in the expression class, which are responsible for creating the various expression objects above, such as calling the Add () method to create an Binaryexpression object that represents an arithmetic addition operation that does not make overflow checking. Calling the lambda method creates a LambdaExpression object that represents a lambda expression, and provides a way for you to review MSDN. We used lambda expressions to construct the expression tree above, and now we look at how to construct an expression tree manually from these expression objects, as shown in the following code:
Static voidMain (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 ();}
The expression tree that is constructed here is still as shown:
Run this code to see what the output is:
As you can see, we did construct the same lambda expression as before, by hand. For an expression tree, it has several more important properties:
Body: Refers to the body part of an expression;
Parameters: Refers to the parameters of an expression;
NodeType: Refers to the node type of an expression, as in the above example, its node type is lambda;
Type: Refers to the static type of the expression, in the example above, type is fun<int,int,int>.
In expression tree Visualizer, we can see the related properties of the expressions directory trees, as shown in:
Expression tree and Delegate
You may often see the following language, where the first sentence is to initialize the Func delegate directly with a lambda expression, and the second sentence constructs an expression tree with a lambda expression, what is the difference between them?
Static void Main (string[] args) { Func<intint int2; Expression<Func<intint int2
In fact, it is obvious to look at IL, where the first sentence directly compiles the lambda expression directly into IL, as shown in the following code:
. methodPrivateHidebysigStatic voidMain (string[] args) CIL managed{. entrypoint. Maxstack3. Locals init ([0]class[System.core] System.Func '3<int32,int32,int32>Lambda) Il_0000:nop il_0001:ldsfldclass[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 Insta NCEvoid class[System.core] System.Func '3<int32,int32,int32>::.ctor (Object, nativeint) il_0014:stsfldclass[System.core] System.Func '3<int32,int32,int32>TerryLee.LinqToLiveSearch.Program::'cs$<>9__cachedanonymousmethoddelegate1'il_0019:br.s il_001b il_001b:ldsfldclass[System.core] System.Func '3<int32,int32,int32>TerryLee.LinqToLiveSearch.Program::'cs$<>9__cachedanonymousmethoddelegate1'Il_0020:stloc.0Il_0021:ret}
In the second sentence, because it tells the compiler that it is an expression tree, the compiler parses the lambda expression and generates an expression tree representing the lambda expression, which is consistent with the IL that we created by manually creating the expression tree, as shown in the following code. Some of the code is omitted here in order to save space:
. methodPrivateHidebysigStatic voidMain (string[] args) CIL managed{. entrypoint. Maxstack4. 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:callclass[Mscorlib]system.type [Mscorlib]system.type::gettypefromhandle (...) Il_000b:ldstr"a"Il_0010:callclass[System.core]system.linq.expressions.parameterexpression [System.core]system.linq.expressions . Expression::P arameter (class[Mscorlib]system.type, Il_0038:callclass[Mscorlib]system.type [Mscorlib]system.type::gettypefromhandle () Il_003d:callclass[System.core]system.linq.expressions.constantexpression [system.core]system.linq.expressions.expr Ession::constant (Object, class[Mscorlib]system.type] Il_0042:callclass[System.core]system.linq.expressions.binaryexpression [System.core]system.linq.expressions.expres Sion::multiply (class[System.core]system.linq.expressions.expression,class[System.core]system.linq.expressions.expression] Il_0047:callclass[System.core]system.linq.expressions.binaryexpression [system.core]system.linq.expressions.express Ion::add (class[System.core]system.linq.expressions.expression,class[System.core]system.linq.expressions.expression] IL_004c:ldc.i4.2Il_004d:newarr [System.core]system.linq.expressions.parameterexpression}
Now I believe we all understand that the difference here is mainly to deepen the difference between the expression directory tree.
Executing an expression directory tree
Before you can construct an expression tree, now look at how to execute the expression tree. We need to call the Compile method to create an executable delegate, and invoke the delegate, as in the following code:
Static voidMain (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 (); intresult = Mylambda (2,3); Console.WriteLine ("Result:"+result. ToString ()); Console.read ();}
Results of output after run:
Here we can simply call the compile method, in fact, in the. NET framework calls an inner class named Expressioncompiler to do the expression tree execution (note that the compiler here is not equivalent to compiler compilation). In addition, you can only execute an expression tree that represents a lambda expression, that is, the lambdaexpression or expression<tdelegate> type. If the expression tree does not represent a lambda expression, you need to call the lambda method to create a new expression. As in the following code:
Static void Main (string[] args) { = Expression.add ( expression.constant (2), expression.constant (3)); Expression<Func<int>> expression = Expression.lambda<Func< int NULL ); Func<int> lambda = expression.compile (); Console.WriteLine (Lambda ());}
Accessing and modifying an expression tree
As I said at the beginning of this article, by parsing the expression tree, we can implement certain functions, since to parse an expression tree, access to an expression tree is inherently unavoidable. In the. NET framework, an abstract expression tree Access class expressionvisitor is provided, but it is a internal that we cannot access directly. Fortunately, in MSDN, Microsoft gives the implementation of the ExpressionVisitor class, which we can use directly. The class is an abstract class, and Microsoft is designed to allow us to implement our own expression tree access classes on the basis of integrated expressionvisitor. Now let's look at the simple expression tree:
Static void Main (string[] args) { Expression<Func<intint int2 ;
After the output is:
Now that we want to modify the expression tree so that it represents a lambda expression (A, B) = (A-()-(2)), then you need to write your own expression tree accessor, as shown in the following code:
Public classoperationsvisitor:expressionvisitor{ Publicexpression Modify (expression expression) {returnVisit (expression); } protected OverrideExpression VisitBinary (binaryexpression b) {if(B.nodetype = =expressiontype.add) {Expression left= This. Visit (B.left); Expression Right= This. Visit (B.right); returnexpression.subtract (left,right); } return Base. VisitBinary (b); }}
Use the expression tree accessor to modify an expression tree, as shown in the following code:
Static void Main (string[] args) { Expression<Func<intint int2 ; var New operationsvisitor (); = operationsvisitor.modify (lambda); Console.WriteLine (Modifyexpression.tostring ());}
You can see the output after running:
It seems that we are modifying the expression tree, in fact, it is not all right, we just modify a copy of the expression tree, because the expression tree is immutable, we can not directly modify the expression tree, to see the implementation of the above Operationsvisitor class we all know, The node in the expression tree that was copied during the modification.
Why do I need an expression directory tree
From the previous introduction, I believe that you have some understanding of the expression tree, there is a very important question, that is why you need an expression directory tree? At the beginning of this article, it was said that by parsing the expression tree, we can implement some of our specific functions, take LINQ to SQL as an example, see the following picture:
When we write a query expression in the C # language, it returns a value of type IQueryable that contains two very important properties, expression and provider, as in the following code:
The query expression we write will be encapsulated as an abstract data structure, which is an expression tree, and when we use the value returned above, the compiler will translate it in the way it expects it to, which is determined by expression and provider. As you can see, this will be very flexible and well-extensible, with an expression tree, you can freely write your own provider, to query the data source we want. It is often said that LINQ provides a unified way of programming to access a variety of data sources, the mystery of which is here. However, it is important to note that LINQ to objects does not require any particular LINQ Provider, because it is not translated into an expression tree, which is later said.
Original link: Build Your own LINQ Provider (top): Expression Tree Secrets
Li Huijun.
[Go] Build Your own LINQ Provider (top): Expression Tree Secrets