This is to be expected with what are essentially pointers to heap objects. The actual code being executed only manipulates machine-word-sized values with the same semantics, so it makes little sense to have copies of the same code.
Well, yes. But you can't devirtualize until you know the types as well as possible, which is after "de-generifying", so as to speak (I don't really know what terminology .NET uses for this).