Consider a program that uses System.Collections.Generic.List<>:
List<int> x = new List<int>(); x.Add(1); x = 2;
After compiling the program, we find CIL instructions to create the generic List<int>, add 1 to the list, and reset the first element in the list to be 2:
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor() IL_0009: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0/*int32*/) IL_0012: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::set_Item(int32, !0/*int32*/)
Notice that in each call, a generic instance is referenced. The signature of the method in the instruction contains the instantiated generic parameters, not the actual generic instance arguments, e.g., “0!”. You may ask: “Why aren’t the generic parameters substituted with the actual argument System.Int32?” The only reason I can think of is so the name/signature encoding can be found in the assembly mscorlib. It also allows for the system to JIT the CIL code with various generic arguments when executed. You can see using DotPeek that in the MemberRef table for the set_Item method, there are three fields used to define the method called: (1) the declaring type System.Collections.Generic.List`<System.Int32>, which is an instantiated generic; (2) the name of the method, set_Item; and (3) the signature blob System.Void (System.Int32, !0). In order to find the CIL for the method in mscorlib, a compiler would need to find a method with the same name and same signature. It’s easier to get a match when the generic parameter is used, and not the generic argument.
The problem with these incomplete signatures is that the generic parameter is already typed. Campy fixes this problem by creating new MethodReference values that fully type the method parameters. It performs unification of signatures instead of a simple string comparison for matching. Thus, System.Collections.Generic.List`1<>::set_Item(Int32, !0) matches System.Collections.Generic.List`1<Int32>::set_Item(Int32, Int32). This change required quite a bit of jumping through hoops because the Mono Cecil’s assembly and metadata resolvers could not be used. I had to write new ones. The next release of Campy will add in this new code.