In CLR 1.0, there was a simple invariant between IL code blob, and native code blob.  It was either 1:0 if the code wasn't jitted, or 1:1 if it was. Method tokens (module scope, mdMethodDef) were also 1:1 with the IL blobs. 1:1 is a nice relationship. Each side can just point to the other without needing to enumerate through a collection.  In this case, you could do a clean mapping from (module, mdMethodDef) <--> IL code blob <--> native code blob.

In CLR 2.0, things got more complicated:

  1. Generics allow 1 IL function to be instantiated and jitted arbitrary number of times to native code. For example, void Foo<T>(T t)  may be the IL function, and it may be instantiated as Foo<int>, Foo<string>, Foo<bar>, etc.   So that makes the IL:Native potentially 1:n, n>= 0.
  2. Edit-and-continue (EnC) allows providing new IL method bodies for an existing mdMethodDef. (It's important to use the existing methodDef instead of creating a new one so that other references to the method pick up the new body). Thus you can start out with Foo() version 1, edit and, and then get Foo() version 2. Each version of Foo() has its own IL blob, but the same mdMethodDef.

Generics + EnC are wonderful features, but they break 1:1 relationships.  We wanted to keep the invariant that the tokens (mdMethodDef) stayed 1:1 with the IL, and ICorDebugFunction was 1:1 with the IL.  


Coping with the breaks
:

Since EnC generated new IL blobs, that meant it would generate new ICorDebugFunctions, which would be provided to the debugger via new debug events (ICorDebugManagedCallback2::FunctionRemapOpportunity and FunctionRemapComplete).  Hence we added ICorDebugFunction2::GetVersionNumber() so that the debugger could tell which EnC version each function was for.

ICorDebugCode (for native code) stayed 1:1 with the actual native code blobs. Thus the 1:1 relationship between ICorDebugCode (native) and ICorDebugFunction got broken in CLR 2.0. A single ICDFunction could now map to multiple ICDCodes (1:n). It turns out for source-level debugging, the design patterns rarely needed to map from ICorDebugCode back to ICorDebugFunction, so that was a pretty reasonable place to put the break.