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:
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.