I am trying to understand AST in C#. I wonder, what exactly Compile()
method from this example does.
// Some code skipped Expression<Func<string, int, int, string>> data = Expression.Lambda<Func<string, int, int, string>>( Expression.Call(s, typeof(string).GetMethod(“Substring”, new Type[] { typeof(int), typeof(int) }), a, b), s, a, b ); Func<string, int, int, string> fun = data.Compile();
To prevent misunderstandings, I understand the Expression.Lambda
and Expression.Call
constructs. What interests me is the Compile()
method. Does it somehow produce real MSIL? Can I see the MSIL?
For Lambda expressions, the compiler doesn't translate them into something which is already understood by JVM. Lambda syntax that is written by the developer is desugared into JVM level instructions generated during compilation, which means the actual responsibility of constructing lambda is deferred to runtime.
Only expression trees that represent lambda expressions can be executed. Expression trees that represent lambda expressions are of type LambdaExpression or Expression<TDelegate>. To execute these expression trees, call the Compile method to create an executable delegate, and then invoke the delegate.
The term 'Lambda expression' has derived its name from 'lambda' calculus which in turn is a mathematical notation applied for defining functions. Lambda expressions as a LINQ equation's executable part translate logic in a way at run time so it can pass on to the data source conveniently.
To create a lambda expression, you specify input parameters (if any) on the left side of the lambda operator and an expression or a statement block on the other side. When you use method-based syntax to call the Enumerable. Select method in the System.
What interests me is the
Compile()
method. Does it somehow produce real MSIL?
Yes. The Compile method runs a visitor over the lambda body block and generates IL dynamically for each subexpression.
If you're interested in learning how to spit IL yourself, see this "Hello World" example of how to use Lightweight Codegen. (I note that if you are in the unfortunate position of having to use Lightweight Codegen in a partially trusted appdomain then things can get a bit weird in a world with Restricted Skip Visibility; see Shawn Farkas's article on the subject if that interests you.)
Can I see the MSIL?
Yes, but you need a special "visualizer". The visualizer I used to debug Compile()
while I was implementing my portions of it can be downloaded here:
http://blogs.msdn.com/b/haibo_luo/archive/2005/10/25/484861.aspx
The answer to this is now partly outdated, in that it's now only sometimes what happens.
Compilation of expressions to IL requires Reflection.Emit which isn't available all the time, particular with AOT. So in those cases instead of compiling to IL the expression is "compiled" to a list of objects representing instructions. Each of these instructions has a Run
method that causes it to carry out the appropriate action, working on a stack of values much as IL works on a stack. A method that calls Run
on these objects can then be returned as the delegate.
Generally running such a delegate is slower than jitting IL, but it's the only option when compiling to IL isn't available, and the compilation step is often faster, so very often the total time of compile + run is less with the interpreter than with IL for one-off expressions.
For that reason, in .NET Core there is now an overload of Compile
that takes a boolean requesting interpretation even if compiling to IL is available.
All of which makes for an interesting mix of languages; Expressions themselves are a language, the assembly is written in C#, it can compile to IL and the interpreted instruction objects constitute a fourth language.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With