As it is usually brought up, not erasing types also comes with some potential cons: namely making CLR languages dependent on C#’s chosen variance model.
In Java, List<Cat> may or may not be the subclass of List<Animal>, but it is up to the List implementor. This way Scala/Kotlin/another JVM language is free to define their own variance model independent of the host language. C# did limit their language ecosystem with it quite a bit. (Afaik Scala for CLR stopped in part due to this).
In C# I believe you can optionally mark generic classes/methods as covariant or contravariant. Is that not enough or does it not get exposed in the CLR or something?
How do two languages with two different variance models interoperate in JVM if they share the same type but expect different behavior? Is it safe to share a list created in one language and pass into another if their variance models differ? Having an explicit variance model makes cross-language interoperability safer and easier (which was one of the main selling points of the Common Language Runtime), doesn't it?
Simply from an empirical point of view, the CLR is pretty much a desert compared to the flourishing JVM one so while it can be attributed to many things, I am really sure that explicit variance is not that attractive for language developers.
Yeah, java can change the variance model used by generics, but my point is that it is a language-level feature, not something fundamental at a JVM level, which is imo the correct decision.
Also, unfortunately arrays are covariant so Cat[] is a subclass of Animal[] (both in Java and C# actually), where your mentioned example indeed introduces a “poisoned” value, waiting for a classcastexception.
In Java, List<Cat> may or may not be the subclass of List<Animal>, but it is up to the List implementor. This way Scala/Kotlin/another JVM language is free to define their own variance model independent of the host language. C# did limit their language ecosystem with it quite a bit. (Afaik Scala for CLR stopped in part due to this).