Did C# really break backwards compatibility of their bytecode? What I understand is that C# generic and non generic APIs are completely different, and are not inter operable.
To the first - yes, the version of .NET that introduced generics broke bytecode backward compatibility. Stuff compiled for .NET 1.0 will not run on the CLR for 2.0 and later. In practice, this wasn't a big deal; maintainers just shipped two different versions of their packages, and you'd download the one you wanted.
To the 2nd - The concrete types are separate, but they fit into a common interface hierarchy, so, at a source code level, they're plenty interoperable. For example, the (non-generic) IEnumerable interface has a Cast<T>() extension method that will convert it to a (generic) IEnumerable<T>. IEnumerable<T>, for its part, simply inherits IEnumerable.
In general, I actually like using those conversion methods for downcasting, because it gives a clearer indication of when I'm in a danger zone - for example, converting a non-generic list to a generic list might fail if the non-generic list contains a mix of different types of object. Java's situation feels less predictable to me, as this article illustrates fairly well. A few keystrokes for the sake of safer code is a dandy tradeoff in my book.
> Stuff compiled for .NET 1.0 will not run on the CLR for 2.0 and later
That's not true. (Mass shared hoster kinda guy here) when we pushed a bunch of customers code compiled for .NET 1.0/1.1 over to hosting environments with only CLR 2 available, their code ran just fine. These were .NET 1.0/1.1 assemblies.
There are some edge cases where stuff could break, say when doing reflection or emitting your own IL, but that for most LOB web hosted apps was pretty rare.
I did in fact marvel how backwards compatible CLR 2 was when it first shipped.
Not sure on the first part but the second part is true.
When there is a C# class that both has and doesn't have type information, they are actually completely different classes that just happen to share a base name.
This really made it painful when they first added generics. If you used generic containers but wanted to call into a library that predated it you hand to transform your data in/out of all the calls to the library. In Java land you didn't have to do anything other then a blind cast on the out side which doesn't even have a runtime penalty.
Long term the C# way was probably better but then they didn't have nearly as robust of a library ecosystem when they added generics.
Yeah, IIRC they added the "constrainted." instruction prefix. It's a prefix to the callvirt instruction that constrains the the underlying call to the specified type, allowing runtime generic type decisions/exceptions rather than only compile time decisions like Java.