It's possible you misunderstand generics. You wrote earlier:
This isn't true, and hasn't been true historically, going back at least to ML in the 70s. Generics in Java (and I think in just about every other language with generics) are a mathematically sound type-system extension, and have nothing to do with code expansion. One property generics normally have is that the type checker can prove that a function which uses generics is type-correct without knowing anything about how it's actually instantiated. This is not true of C++, and is something that I consider to be a
massive flaw in its approach to generics, one which is still waiting to be fixed with Concepts.
Furthermore, it's generally expected that type-checking of generics should always halt. Personally, I want my type-checker to halt pretty quickly. I also want it to support separate compilation, so it's not forced to re-expand templates from other modules every time I make a change in how I've used them. The approach taken by C++ here is directly responsible for its long build times. In Java, separate compilation is forced on you because it's expected that everything is hot-loadable.
Finally, in both Java and C#, the designers were upfront in saying that they wanted to support
polymorphic recursion. It's simply not possible to do this by treating generics as templates: it will always just send the compiler into an infinite loop.
When generics are not implemented as code expansion, you need to think harder about how they are implemented, especially in languages like Java and C# which are supposed to support runtime type information and runtime loading of classes. Java made things somewhat harder by not putting generics into the CLR. But the other constraints they were working with are real constraints on absolutely solid type theory.