| I haven't written C# professionally since the early 2010s but back then the language had a big problem in that the old Container classes were not compatible with the Container<X> classes that were added when they added generics to C#. This created an ugly split in the ecosystem because if you were using Container<X> you could not pass it to an old API that expected a Container. Java on the other hand had an implementation of generics that made Container<X> just a Container so you could mix your old containers with generic containers. Now Java's approach used type erasure and had some limitations, but the C# incompatibility made me suffer every day, that's the cultural difference between Java and a lot of other languages. It's funny because when I am coding Java and thinking just about Java I really enjoy the type system and rarely feel myself limited by type erasure and when I do I can unerase types easily by - statically subclassing GenericType<X> to GenericType<ConcreteClass> - dynamically by adding a type argument to the constructor - mangling names (say you're writing out stubs to generate code to call a library, you can't use polymorphism to differentiate between Expression<Result> someMethod(Expression<Integer> x)
and Expression<Result> someMethod(Expression<Double> x)
since after erasure the signature is the same so you just gotta grit your teeth and mangle the method names)but whenever I spend some time coding hard in a language that doesn't erase generic parameters I come back and I am not in my comfortable Java groove and it hurts. |
If you're up for it you should give it another try. Your example of subclassing GenericType<X> and GenericType<ConcreteClass> may be supported with covariance and contravariance in generics [1]. It's probably not very well known among C# developers (vs. basic generics) but it can make some use cases a lot easier.
[1] https://learn.microsoft.com/en-us/dotnet/standard/generics/c...