The other day I needed a component for a project of mine, which would rewrite methods into state machines like the CTP C# compiler does for
async methods. The rewriter would accept a callback to identify the sites where a continuation must be created, and another one to emit glue code for the site using two primitives:
SAVE_STATE() which returns the continuation delegate and
RESTORE_STATE() which returns the value passed to the continuation. This approach permits the users of the rewriter to avoid the overhead of saving and restoring state when the continuation turns out to be unnecessary (in
async terms, when the awaited thing is already complete). The new CTP compiler implements this optimization. My component would eventually have to work with IL methods either via Reflection.Emit or Cecil, but I thought it would be interesting to make it work with LINQ Expressions first. Besides, the DLR has a similar rewriter for
yield which I could scavenge for useful hints. The DLR rewriter uses a nested lambda to create the 'environment', shifting the work to the LINQ Expression compiler (EC). Probably because of permission issues, EC does not create new closure classes and instead uses a thinly veiled
object to store closure locals. I did not want this, I wanted to generate a proper closure class. After all, EC can compile a lambda expression into a
Although I more-or-less made it work, I must report that LINQ Expressions don't work all that well with Reflection.Emit:
LambdaExpressioncannot have parameters of unbaked type. EC appears to have no problems with this, but
Expression.CreateLambdauses generic lambda factories and an unbaked type cannot serve as a type argument. One must have the lambda accept a suitable base class or
It is impossible to generate a call to any method which has not been 'baked' because LINQ Expression constructors insist on validating a method's parameters and call
MethodBase.GetParameters(). This extends to object construction as
Expression.Newvalidates constructor parameters. These two problems are very obnoxious and make any serious use of LINQ Expressions with Reflection.Emit impossible.
As a side note, I find it puzzling and inconvenient that
MethodBuilderdoes not expose its parameter list.
EC has no problems emitting constants referring to a
MethodBuilder, but one must tell
Expression.Constantthe correct types, viz.
MethodInfo. Otherwise EC happily emits casts to
TypeBuilderwhich blow up at runtime. Just a minor gotcha, but still.
There is no
DelegateCreationExpression. This is strictly speaking not related to Reflection.Emit, but it is annoying to have to generate a call to
CreateDelegate, complete with casts and stuff, instead of a
EC cannot compile lambda expressions into member methods even if the method signature is compatible. The target method has to be static, period.
This was a useful excercise and working directly with IL should not be much more complicated.