Why you can’t do Edit-and-Continue on Dynamically generated code

I gave a brief example of how you can debug dynamically generated code (e.g., code generated via Reflection.Emit). Jamie Cansdaleobserved that you can’t use EnC with dynamically generated code. This was a conscious choice. Here are some reasons for it:

It was not a core EnC scenario. For v2.0, we wanted to focus just on the core EnC scenarios and make sure we got them right. This meant cutting all edge-cases where we didn’t think the cost-benefit ratio made sense. This is actually the single-biggest reason we didn’t do it.

There’s sort of a workaround: You can fake EnC for dynamic methods by just re-emitting a new method. For example, callers could go through a delegate instead of a direct call to the method. When the you EnC the method, you’d update the delegate to point to the most recent version.

There are also some fundamental problems: Even if it were a core scenario, there are some innate issues that would need to be addressed with doing EnC on dynamic methods:

1) EnC requires strong support from a language service and good integration with the IDE. Ref.Emit is not tied to any language service and has no hooks for IDE integration.

o For example, the IDE / Compiler may choose to track more information about EnC-able functions than just the sequence map provided by Emit. This can help it do things like rude-edit detection, or perhaps provide better remap information.

o The debugger needs to be able to reinvoke the compiler (eg, vbc.exe, csc.exe, etc) to produce new IL for an edit. With Emit, the “compiler” is inaccessible to the debugger because it’s effectively hidden in the program.

2) Assuming the emitted code somehow mapped up to a particular compiler is too fragile. We can’t just pretend the emitted code is C# and have CSC.exe emit a new body:

o How would the IDE know what language it is, or what compiler (csc.exe) to use? There may be many compilers for a single language (eg, multiple C# implementations).

o This is switching compilers midstream. The original compiler is whatever is using Ref.Emit in the code; and the new compiler would be csc.exe. We can claim that the ref.emit routine is emitting C#, but how do we really guarantee that?

3) For EnC, the compiler needs to be able to replace the entire method body with each edit. That means if an edit just adds a new line, the compiler still has to be able to regenerate the rest of the method too. The compiler may not be able to do this.

o What if the Emitted code has constructs that the compiler can’t express (eg, IL filters for C#)?

o What if the code calls other dynamic functions generated by Ref-emit that the compiler doesn’t even know about? There would need to be some way of notifying the compiler about newly generated dynamic functions. Emit achieves this via resolution events (AppDomain.TypeResolve, AppDomain.AssemblyResolve, etc), but those are unavailable to a command-line compiler.

For similar reasons, we also disallow EnC with in-memory modules. Mainly it wasn’t a core scenario, and has some of the same fundamental problems as dynamic code.