Overview
In. net Framework 3.5, with the support of LINQ, has won the favor of developers with its powerful and elegant programming methods, and various LINQ providers are even more powerful, such as LINQ to nhib.pdf and LINQ to Google. As well as good scalability, we can easily write our own LINQ provider.
This article builds your own LINQ provider SeriesArticleArticle 1 describes the knowledge about the expression tree.
Recognize the expression directory tree
What is the expression tree? It is an abstract syntax tree or a data structure. By parsing the expression tree, some of our specific functions can be implemented (as will be discussed later). First, let's take a look at how to construct an expression directory tree. The simplest way is to use lambda expressions.Code:
Expression<Func<Int,Int,Int> Expression = (a, B) => a * B + 2;
When we specify a Lambda expression to a variable (parameter) of the expression <tdelegate> type, the compiler will issue a command to generate the expression directory tree, as shown in the lambda expression (, b) => a * B + 2 creates an expression directory tree, which represents a data structure, that is, we represent a line of code in the form of a data structure, specifically, the final constructed expression directory tree shape is shown in:
Each node represents an expression, which may be a binary operation or a constant or a parameter. For example, parameterexpression is a parameter expression and constantexpression is a constant expression, binaryexpression is a binary expression. You can also use Expression Tree Visualizer in Visual Studio to view the expression directory tree:
Shows the result:
Expression Tree Visualizer can be obtained from the LINQ sample on the msdn code Gallery. Now we know the composition of the expression directory tree to see which expressions are provided by. NET Framework? As shown in:
They all inherit from abstract base class expressions, while generic expressions <tdelegate> inherit from lambdaexpression. A large number of factory methods are provided in the expression class. These methods are used to create the above expression objects, such as calling add () the method will create a binaryexpression object that represents the arithmetic addition operation without Overflow check. Calling the LAMBDA Method will create a lambdaexpression object that represents the lambda expression. For details, refer to msdn. The Lambda expression is used to construct the expression directory tree. Now we can see how to manually construct an expression directory tree using these expression objects, as shown in the following code:
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 () ;}
The constructed expression directory tree is still shown in:
Run this code to see what is output:
We can see that through manual construction, we have indeed constructed the same Lambda expression as above. For an expression directory tree, it has several important attributes:
Body: the body of the expression;
Parameters: the parameter of the expression;
Nodetype: indicates the node type of the expression. In the above example, its node type is lambda;
Type: the static type of the expression. In the preceding example, type is Fun <int, Int, int>.
In Expression Tree Visualizer, we can see the related attributes of the expression directory tree, as shown in:
Expression directory tree and delegate
You may often see the following languages. The first sentence is to initialize the func delegate using a Lambda expression, and the second sentence uses a Lambda expression to construct an expression directory tree, what is the difference between them?
Static voidMain (String[] ARGs ){Func<Int,Int,Int> Lambda = (a, B) => A + B * 2;Expression<Func<Int,Int,Int>> Expression = (a, B) => A + B * 2 ;}
In fact, let's take a look at Il. The first sentence directly compiles the lambda expression into Il, as shown in the following code:
. Method Private Hidebysig Static void Main ( String [] ARGs) Pencil managed {. entrypoint. maxstack 3. Locals Init ([0] Class [System. Core] system. func '3 <int32, int32, int32> lambda) il_0000: NOP il_0001: lds1 _ 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: lds1 _ Class [System. Core] system. func '3 <int32, int32, int32> terrylee. linqtolivesearch. Program :: 'Cs $ <> 9 _ cachedanonymousmethoddelegate1' Il_0020: stloc.0 il_0021: Ret}
The second sentence tells the compiler that it is an expression directory tree. Therefore, the compiler analyzes the lambda expression and generates an expression directory tree that represents the lambda expression, that is, it is consistent with the Il generated by the directory tree that we created manually. The following code is shown. Some code is omitted to save space:
. Method Private Hidebysig Static void Main ( String [] ARGs) Pencil 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 "" 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}
I believe everyone can understand the differences between them here to deepen the differences between the expression directory tree.
Execution expression directory tree
We can create an expression directory tree. Now let's see how to execute the expression directory tree. We need to call the compile method to create an executable delegate and call the delegate, as shown in the following code:
Static void Main ( String [] ARGs ){ Parameterexpression Paraleft = Expression . Parameter ( Typeof ( Int ),"" ); 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 ();}
Output result after running:
Here, we only need to simply call the compile method. In fact. net Framework calls an internal class named expressioncompiler to execute the expression directory tree (note that compiler here is not the same as compiler compilation ). In addition, you can only execute the expression directory tree that represents the lambda expression, that is, the lambdaexpression or expression <tdelegate> type. If the expression directory tree does not represent a Lambda expression, you must call the LAMBDA Method to create a new expression. The following code:
Static voidMain (String[] ARGs ){BinaryexpressionBody =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 ());}
Access and modify the expression directory tree
As I mentioned at the beginning of this article, by parsing the expression directory tree, we can implement some specific functions. to parse the expression directory tree, access to the expression directory tree is naturally inevitable. In. NET Framework, an abstract expression expressionvisitor class is provided in the directory tree, but it is an internal and cannot be accessed directly. Fortunately, Microsoft provides the expressionvisitor class implementation in msdn, which can be used directly. This class is an abstract class. Microsoft aims to implement its own expression directory tree metadata class based on the integration of expressionvisitor. Now let's look at the simple expression directory tree:
Static voidMain (String[] ARGs ){Expression<Func<Int,Int,Int> Lambda = (a, B) => A + B * 2;Console. Writeline (lambda. tostring ());}
Output:
Now we want to modify the expression directory tree so that the lambda expression it represents is (a, B) => (a-(B * 2 )), in this case, you need to write your own expression directory tree accesser, as shown in the following code:
Public classOperationsvisitor:Expressionvisitor{PublicExpressionModify (ExpressionExpression ){ReturnVisit (expression );}Protected overrideExpressionVisitbinary (BinaryexpressionB ){If(B. nodetype =Expressiontype. Add ){ExpressionLeft =This. Visit (B. Left );ExpressionRight =This. Visit (B. Right );ReturnExpression. Subtract (left, right );}Return Base. Visitbinary (B );}}
Use the expression directory tree accessor to modify the expression directory tree, as shown in the following code:
Static voidMain (String[] ARGs ){Expression<Func<Int,Int,Int> Lambda = (a, B) => A + B * 2;VaROperationsvisitor =NewOperationsvisitor();ExpressionModifyexpression = operationsvisitor. Modify (lambda );Console. Writeline (modifyexpression. tostring ());}
After running, you can see the output:
It seems that we are modifying the expression directory tree, but we are not all right. We just modify a copy of the expression directory tree, because the expression directory tree is immutable, we cannot directly modify the expression directory tree. You can see the implementation of the operationsvisitor class above. During the modification, we copied the node of the expression directory tree.
Why expression directory tree?
Through the previous introduction, I believe you have some knowledge about the expression directory tree. Another important question is why the expression directory tree is required? At the beginning of this article, we mentioned that by parsing the expression directory tree, we can implement some of our specific functions. Take the LINQ to SQL as an example. Let's look at the figure below:
When we compile a query expression in C #, it returns a value of the iqueryable type, which contains two important attributes: expression and provider, the following code:
the query expression we wrote will be encapsulated into an abstract data structure, which is the expression directory tree. When we use the value returned above, the compiler will translate the value in the desired way, which is determined by expression and provider. As you can see, this will be very flexible and highly scalable. With the expression directory tree, you can freely compile your own provider to query the desired data source. It is often said that LINQ provides a unified programming method for accessing different data sources, and its mysteries are here. However, it should be noted that no specific LINQ provider is required for the LINQ to objects, because it is not translated as the expression directory tree, which will be mentioned later