For LINQ to be able to translate your queries into a different system at runtime it must be able to preserve the syntax of your operations rather than emitting code to perform it in-memory. Rather than trying to reverse-engineer compiled code at runtime the compiler instead preserves the intent of your code in an "expression tree".
When a function or variable expects a LambdaExpression
- normally via the sub-type Expression<Func<T>>
- the compiler parses the code as normal but rather than generating Intermediate Language (IL) instructions as normally it instead generates code that re-builds the code as an "expression tree".
This expression tree captures all the intent of the code you wrote in a way that LINQ providers can examine and then produce their own interpretation of (e.g. SQL) for remote execution (e.g. PostgreSQL).
Notes
Expression trees:
- can still be compiled and executed by using the
Compile
method on them - are immutable (they cannot be changed once built)
- Expression Visitor lets you effectively substitute expressions with new expressions
- these visitors are combined by nesting them at the top level
- are not limited to LINQ - you can just take a function, examine it, modify it and compile it at runtime!