If I attempt to generalize it, I think most of C#'s differences in language design that have opposites in Java are superior. Examples include: + not virtual by default (so base classes can be changed more easily without breaking/recompiling downstream clients that the base class writer doesn't know about; specifying something as "virtual" should be a deliberate conscious decision by the class author) + value types (f…
Not having type erasure is pretty nice (except that reflection code with generics is really hairy).
I was wrong, reflecting on the .NET design choices
51–60 of 139 posts
Re: I was wrong, reflecting on the .NET design choices
#52This, more than anything, has dramatically improved the quality of my designs... and made coding fun again.
Immutability and Lambda functions have also had a tremendous impact on my designs.
Is the term Object-Oriented-Programming relevant anymore?
Re: I was wrong, reflecting on the .NET design choices
#53Earlier quoted context omitted.
What do you think of partial classes and methods in term of code quality?
I've always thought of "partial classes" as a language feature motivated by code generators not stomping on programmers' manually entered code. E.g. Winforms generates some declarations in one partial class while the programmer codes UI handlers in the other partial class.
Re: I was wrong, reflecting on the .NET design choices
#54Earlier quoted context omitted.
> + no checked exceptions (checked exceptions have benefits in theory but real-world practice shows that it forces programmers to copy-paste mindless boilerplate to satisfy the checked-constraint) As languages like Rust or Swift demonstrate, the issue is less the checked exceptions and more the abject lack of support for them in the language and type system, which ends up making them essentially unusable. > Gosling s…
Neither Swift nor Rust have exceptions, checked or otherwise. The kind of exceptions that unwind the stack until some part of the code up the stack catches the exception. They both handle errors by returning error values, kind of like Go. Rust has a try! macro, which might make you think it's try/catch equivalent, but it's not. It's just a syntactic sugar over error values. Similarly in Swift try/catch/throw is just…
Cast a large double to an int -- crash. How do you catch that error? You can't. You have to make sure it never happens yourself. The UserDefaults is another great one. All sorts of ways that it can crash your app, none of them can be caught or handled. Your app just crashes. My advice: convert all your object to a text string (like json) and store that. Do NOT store a dictionary representation.
Re: I was wrong, reflecting on the .NET design choices
#55Earlier quoted context omitted.
Neither Swift nor Rust have exceptions, checked or otherwise. The kind of exceptions that unwind the stack until some part of the code up the stack catches the exception. They both handle errors by returning error values, kind of like Go. Rust has a try! macro, which might make you think it's try/catch equivalent, but it's not. It's just a syntactic sugar over error values. Similarly in Swift try/catch/throw is just…
> Neither Swift nor Rust have exceptions, checked or otherwise. The point is that both of them have generic error types which "infect" every caller (transitively) in much the way checked exceptions do. That Rust and Swift require explicitly bubbling error values should be a point in favour of checked exceptions.
I thought the explicit bubbling was the nicest thing about error handling in Rust. It's usually just a single character ('?') that the editor can easily highlight, and nicely indicates where the operations are that might fail.
I'd add that checked exceptions don't play nicely with general functional/stream operations like "map" which is why Java went with unchecked exceptions for their streams api in Java 8. Rust on the other hand can handle interior failures in such functions nicely, promoting them to a single overall failure easily via collect(), using the blanket FromIterator> implementation for Result.
Re: I was wrong, reflecting on the .NET design choices
#56Java and C# are seen as "old news" by some here on HN but there's a trove of software engineering wisdom in there. It was a huge boon for Microsoft to be able to learn from Sun's mistakes when they were designing a language that, on paper, is basically the same thing. C#, Java, and Go are all "wonderfully boring" languages which is a divisive topic, but they're all very good at being boring languages. It's not just l…
Re: I was wrong, reflecting on the .NET design choices
#57Earlier quoted context omitted.
> I believe the explosion of factories, abstract factories, and just generally over-engineered object construction and initialization schemes in Java and C# would have been side-stepped if both languages had always had a proper metaclass hierarchy paralleling the regular class hierarchy, as well as some form of local type inference. That's a bit harsh. "Factory" is a term that became prominent in Java as a result of…
First class functions aren't replacements for factories. An abstract factory provides several methods for constructing related objects of different types. The objects come from different type hierarchies, whose inheritance structures mimic each other. For instance, you might have a hierarchy of EncryptionStream and EncryptionKey objects. Both derive in parallel into AESEncryptionStream and AESEncryptionKey. Then you…
> If we can just hold a tuple of classes, and ask each one to make an instance, then that kind of makes AbstractFactory go away.
That seems like just one particular way to solve things. I guess I don't see what the fuss is about, if we are talking about metaclasses in particular, because we could also solve this problem with generics, and the factory solution doesn't seem that bad to begin with.
> Thus all constructors effectively have the same type signature.
Or turned around, the type system is not expressive enough to assign different types to different constructors, and is incapable of distinguishing them. This matches with my general experience, that metaclasses are useful on the dynamic typing side (Python, Lisp, Smalltalk, JavaScript) but annoying on the static typing side (C++, Haskell, C#).
But of course that makes sense. In a system without static types, the only way to pass a class to a function is through its parameters, so you have to pass the class by value. In systems with static typing, you have the additional option of passing a class through a type parameter, which has the advantage of giving you access to compile-time type checking. Furthermore, there are real theoretical problems with constructing type systems which allow you to use metaclasses involving whether the type checker is sound and whether it will terminate.
Re: I was wrong, reflecting on the .NET design choices
#58Re: I was wrong, reflecting on the .NET design choices
#59While I'm inclined to suspect that non-virtual by default is better from a design perspective‡, don't assume the point about performance is overwhelming. HotSpot has done devirtualization for a long time. You can detect not only when a method is never overridden, but also when it's never overridden at a particular call site. A virtual method that's never overridden can sometimes have no extra overhead, while a virtua…
Re: I was wrong, reflecting on the .NET design choices
#60Earlier quoted context omitted.
I'm not sure C# is even really in the "boring" category; they were way ahead of Java 8 with the functional collection stuff in Linq and they're borrowing lots of concepts from Scala for the latest versions.
In addition to the functional stuff, C# has a lot of syntactic sugar that I wouldn't associate with a "boring" language. If you aggressively use all the syntactic sugar from the latest version of C# and compare that to similarly up-to-date Java code, they'll be worlds apart. The C# will look terse, and to more conservative programmers, rather weird.
Btw, i kinda like java's Anonymous Classes. Do we know any reason for C# not to adopt this as well?