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 MethodBuilder
!
Although I more-or-less made it work, I must report that LINQ Expressions don't work all that well with Reflection.Emit:
A
LambdaExpression
cannot have parameters of unbaked type. EC appears to have no problems with this, butExpression.CreateLambda
uses generic lambda factories and an unbaked type cannot serve as a type argument. One must have the lambda accept a suitable base class orobject
.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 asExpression.New
validates 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
MethodBuilder
does not expose its parameter list.EC has no problems emitting constants referring to a
TypeBuilder
or aMethodBuilder
, but one must tellExpression.Constant
the correct types, viz.Type
andMethodInfo
. Otherwise EC happily emits casts toTypeBuilder
which 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 toCreateDelegate
, complete with casts and stuff, instead of aldftn
-new
combo.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.
1 comment:
I've similarly found it frustrating that the authors of Linq Expressions took it 99% of where it needed to go to be a totally general replacement for method body IL emission, but stopped annoyingly short. I was also annoyed to find that calling into a MethodBuilder is similarly prevented by something so trivial as generics validation, though it's hard to know for sure without getting past that error. I don't see any reason it wouldn't work though, because an Emit(OpCodes.Call, ...) works just fine to a MethodBuilder.
Now that everything is open source, we could probably fix it I suppose; I'd be more more inclined to if I could actually justify the time as part of my current client work, but it's not like I can tell my client, "oh, you just need to wait until they release the next .NET runtime." :-)
Your friend in the misery of other people's frameworks,
-Nate
Post a Comment