Earlier quoted context omitted.
When I first learned C#, I was having a hard time to navigate the code due to partial methods. So I have always wondered what C# developers think of having their methods spread out in different files.
Partial classes and partial methods exist specifically to support having code that is automatically generated paired with a human-managed source file. While you could use the support for them to split human-managed code across separate source files, that would be a horrible practice that I've never encountered in the wild, even in .NET shops with otherwise-atrocious practices and code quality.
I was wrong, reflecting on the .NET design choices
31–40 of 139 posts
Re: I was wrong, reflecting on the .NET design choices
#32If 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…
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 said that unsigned types are confusing and dangerous
He is right of course, but he forgets that so are signed types if they're bounded.
If Java had unbounded signed integers (à la Python or Erlang) that'd be one thing, but Java does not, and neither does it have Pascal-style restrictions/user-defined integral bounds, which means it's no less confusing or dangerous, it's just more limited.
Re: I was wrong, reflecting on the .NET design choices
#33http://insightfullogic.com/2014/May/12/fast-and-megamorphic-...
‡ I've used non-OO languages, but never an OO language without virtual by default.
Re: I was wrong, reflecting on the .NET design choices
#34I like the Java convention, for one thing, because it is one less decision for programmers to make. I've seen many C# programmers who are oblivious to what virtual means.
Re: I was wrong, reflecting on the .NET design choices
#35One of the greatest lingering flaws in both C# and Java is the lack of metaclasses. Because classes aren't real objects and therefore not necessarily also instances of other classes (their metaclasses) as they would be in Smalltalk, there is no class-side equivalent of "self/this," nor of "super." In effect, you cannot write static (class) methods that call other static methods without explicitly referencing the clas…
> 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…
The client just knows that it has an EncryptionFactory which makes some kind of stream and some kind of key, which are compatible.
AbstractFactory doesn't specifically address indirect construction or indirect use of a class, but it does solve a problem that can also be addressed with metaclasses. 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.
The thing is that in a language like Java, these factories have rigid methods with rigid type signatures. The MakeKey of an EncryptionFactory will typically take the same parameters for all key types. The client doesn't know which kind of stream and key it is using, and uses the factory to make them all in the same way, using the same constructor parameters (which are tailored to the domain through the EncryptionFactory base/interface).
If we have a class as a first class object (such as an instance of a metaclass), that usually goes hand in hand with having a generic construction mechanism. For instance, in Common Lisp, constructor parameters are represented as keyword arguments (a de facto property list). That bootstraps from dynamic typing. All object construction is done with the same generic function in Common Lisp, the generic function make-instance. Thus all constructors effectively have the same type signature.
Without solving the problem of how to nicely have generic constructors, simply adding metaclasses to Java would be pointless. This is possibly a big part of the reason why the feature is absent.
Re: I was wrong, reflecting on the .NET design choices
#36If 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…
> + 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…
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 syntactic sugar for handling error values.
https://doc.rust-lang.org/book/error-handling.html
https://developer.apple.com/library/content/documentation/Sw...
Re: I was wrong, reflecting on the .NET design choices
#37Java is not "virtual by default", it is virtual-only, except for private methods, which don't participate in inheritence. I like the Java convention, for one thing, because it is one less decision for programmers to make. I've seen many C# programmers who are oblivious to what virtual means.
Re: I was wrong, reflecting on the .NET design choices
#38Earlier quoted context omitted.
Partial classes and partial methods exist specifically to support having code that is automatically generated paired with a human-managed source file. While you could use the support for them to split human-managed code across separate source files, that would be a horrible practice that I've never encountered in the wild, even in .NET shops with otherwise-atrocious practices and code quality.
>While you could use the support for them to split human-managed code across separate source files, that would be a horrible practice To continue that type of advice, some say "#region/#endregion" is another language feature that's intended for code generators so that the IDE can collapse specific lines of code and hide it from view. Programmers should not be hand-coding "#region" themselves. That said, there is deba…
Re: I was wrong, reflecting on the .NET design choices
#39Earlier 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…
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.
Re: I was wrong, reflecting on the .NET design choices
#40While 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…
At the end of the day, the fastest code is one that doesn't have to run.
HotSpot is an impressive technology but the optimizations it has to do to overcome Java's design really only pay for themselves in most frequently executed code paths and only after some time to gather necessary info to perform the optimizations.
It's ok for long-running server code but not good for, say, short-lived command-line program.
Or to put it differently: a language that has perf-friendly design, like Go, matches Java's speed with 10% of engineering time and resources spent on the compiler and optimizations. Perf friendly design means it has to do 10% of the work to achieve the same end result.