Remix.run Logo
PaulHoule 2 days ago

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.

Rohansi 2 days ago | parent | next [-]

This hasn't been an issue for a long time because nobody uses the non-generic collections anymore. That doesn't help you with your reliance on type erasure though.

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...

PaulHoule 2 days ago | parent | next [-]

Yeah, I had a chance a few months back when I was the backup programmer in a game development hackathon and my team was developing with Unity which uses C#. It was fun.

People talk about tradeoffs with GC, the worst one is that I've seen an occasional game that has a terrible GC pause, for instance Dome Keeper based on Godot which also runs in .NET. I used play a lot of PhyreEngine (also .NET) games on the Playstation Vita and never noticed GC pauses but I think those games did a gc on every frame instead of letting the garbage pile up.

Rohansi 2 days ago | parent | next [-]

Whether you're programming with a GC or without one it all comes down to profiling and optimization (when required). Using a GC means you need to think about memory allocations and reduce them (when required) because they factor into how long the GC will need to run. C# is a great language for this because it provides a lot of options for removing GC memory allocations from your code entirely (struct types, Span<T>, etc).

Not all GCs are created equally either. Unity, for example, is based on an ancient version of Mono and so it uses the Boehm GC which is significantly slower than the one used by .NET. Godot probably has two GCs because it primarily runs GDScript (their custom language) and only supports using .NET in a separate engine build. They'll all have their own performance characteristics that the developer will need to adjust for.

uyjulian 2 days ago | parent | prev [-]

PhyreEngine doesn't use .NET, and it is written in C++ (with optional Lua for scripting). You might be thinking of PlayStation Mobile, which does use .NET for scripting.

feoren 2 days ago | parent | prev [-]

Covariance and contravarience is very useful, but it's quite annoying that it can only be used with interfaces and not classes/structs. I write lots of little monoid-like data wrappers like Named<T>, and I have to either go through the hassle of dealing with interfaces for something that shouldn't need it, or I have to abandon covariance. The .NET maintainers seem to think that nobody wants covariance on classes, but it would be very helpful to me.

pohuing a day ago | parent | prev | next [-]

> 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.

Correct me if I'm wrong but allowing this would mean the called api might insert objects wholly unrelated to X. This would break every assumption you make about the container's contents. Why would this ever be allowed or wanted?

mrsmrtss 2 days ago | parent | prev [-]

And yet, Java is trying desperately to fix its generics now with project Valhalla (although value types are their main goal), but it's almost impossible to do without major breaking changes. Without reified generics there's major performance cost when primitive (or value) types are used as generic parameters, because of boxing. Also, .NET has had generics since 2005 when .NET Framework 2.0 was released.