Parameter | Details |
---|---|
TDelegate | The delegate type to be used for the expression |
lambdaExpression | The lambda expression (ex. num => num < 5 ) |
Expression trees are all about consuming "source code" at runtime. Consider a method which calculates the sales tax due on a sales order decimal CalculateTotalTaxDue(SalesOrder order)
. Using that method in a .NET program is easy — you just call it decimal taxDue = CalculateTotalTaxDue(order);
. What if you want to apply it to all the results from a remote query (SQL, XML, a remote server, etc)? Those remote query sources cannot call the method! Traditionally, you would have to invert the flow in all these cases. Make the entire query, store it in memory, then loop through the results and calculate tax for each result.
Expression trees are data structures in a format of a tree, where each node holds an expression. They are used to translate the compiled instructions (like methods used to filter data) in expressions which could be used outside of the program environment such as inside a database query.
The problem here is that a remote query cannot access our method. We could avoid this problem if instead, we sent the instructions for the method to the remote query. In our CalculateTotalTaxDue
example, that means we send this information:
With those instructions, the remote query can perform the work as it's creating the data.
There are two challenges to implementing this. How do you transform a compiled .NET method into a list of instructions, and how do you format the instructions in a way that they can be consumed by the remote system?
Without expression trees, you could only solve the first problem with MSIL. (MSIL is the assembler-like code created by the .NET compiler.) Parsing MSIL is possible, but it's not easy. Even when you do parse it properly, it can be hard to determine what the original programmer's intent was with a particular routine.
MethodCallExpression
has reference to 1) the MethodInfo
it is going to call, 2) a list of Expression
s it will pass to that method, 3) for instance methods, the Expression
you'll call the method on. You can "walk the tree" and apply the instructions on your remote query.
Delegate
type (including Action
or Func
), the compiler converts the lambda expression into a delegate. If it's a LambdaExpression
(or an Expression<Action<T>>
or Expression<Func<T>>
which are strongly typed LambdaExpression
's), the compiler transforms it into a LambdaExpression
. This is where the magic kicks in. Behind the scenes, the compiler uses the expression tree API to transform your lambda expression into a LambdaExpression
.
Lambda expressions cannot create every type of expression tree. In those cases, you can use the Expressions API manually to create the tree you need to. In the Understanding the expressions API example, we create the CalculateTotalSalesTax
expression using the API.
NOTE: The names get a bit confusing here. A lambda expression (two words, lower case) refers to the block of code with a =>
indicator. It represents an anonymous method in C# and is converted into either a Delegate
or Expression
. A LambdaExpression
(one word, PascalCase) refers to the node type within the Expression API which represents a method you can execute.
One of the most common uses of expression trees is with LINQ and database queries. LINQ pairs an expression tree with a query provider to apply your instructions to the target remote query. For example, the LINQ to Entity Framework query provider transforms an expression tree into SQL which is executed against the database directly.
Putting all the pieces together, you can see the real power behind LINQ.
products.Where(x => x.Cost > 5)
SELECT * FROM products WHERE Cost > 5
ExpressionVisitor
) and make the wanted changes.