Hacker News new | past | comments | ask | show | jobs | submit login

Fundamentally, Java and C# are extremely similar. What's interesting is how different their philosophies are: Java really values backwards compatibility, does not evolve very fast and tries to hide and features that could cause a programmer to shoot himself in the foot. C#, on the other hand, has had some compatibility breaking changes, has evolved quickly with fancy research features (e.g. Linq is actually much like a monad with SelectMany as bind) and has a bunch of "dangerous" features like operator overloading and unsafe regions.

EDIT: Maybe there weren't actually any language changes breaking backwards compatibility. I'm not sure.

I personally like C#'s philosophy better: PL research exists for a reason and newer features make coding easier; moreover, I can avoid doing really stupid things and so can be trusted with operator overloading! Others maintain the opposite and believe Java's more conservative approach is best.

It would be nice to compare how the two philosophies fare in getting their respective languages adapted, but I fear such a comparison would be flawed: C# is much too tied to Microsoft, so most people not keen on heavy coupling with any particular vendor or OS tend to avoid it for that reason. The opposite is also true: heavy MS shops are much more likely to choose C# just because it's part of .NET, and not because of the language characteristics.

Still, it is very interesting to see what has happened given two virtually identical languages with different backers and philosophies.




     [Java] does not evolve very fast
While this is true for Java the language, it's not true for Java the VM, which evolved a lot faster than the CLR.

And because Java the language does not evolve very fast, the ecosystem has given birth to mature JVM compilers/interpreters for languages such as Clojure, Scala, JRuby and Groovy, amongst others.

The biggest difference between Java and .NET is that the "backers" of Java come from a really big and open-source friendly community.


For most part of the programming world, Java is synonymous with, Java the programming language, not JVM.

I always thought Java went very heavy on the frameworks side. Evolution was faster on the frameworks end.


I think it's irrelevant what people associate Java with, as long as its success can be attributed partly to the awesomeness of the JVM. There's no other mainstream VM out there that does the optimizations that the JVM is doing, or that has the awesome garbage collectors that the JVM has.

Have you seen the details of the new Java G1 garbage collector or the specs for JSR-292 (InvokeDynamic)? That's awesome evolution right there.


I am not saying JVM isn't awesome. But JVM isn't anymore about Java, than JavaScript is about Java.

Its like calling the Pentium/AMD/ARM as C processing Machines.

The name is deceptive, JVM based languages gives an impression that the languages have to do some thing with Java. Sure they have Java interoperability, but that is different to liking them them with the 'Java programming language' brand. Since JVM was shipped to run Java programs, it was called Java virtual machine.

Otherwise its a virtual machine optimized to run Java programs. When people think of Java its 'Java the language'.

JVM improvements are not necessarily improvement on the Java programming language syntax and semantics.


Well, .NET does have stuff like Iron{Ruby, Python, Whatever}. It also has F#, which is awesome, and VB, which isn't.

Also, Java is now backed by Oracle which isn't the most friendly of open source companies either.

However, I was actually only really talking about Java the language and not the JVM.


Not to be negative here, but compare JRuby with IronRuby ... while IronRuby is basically unusable and almost dead, JRuby is not only really mature, but also the fastest Ruby implementation available. A lot of Ruby apps are right now deployed on top of Java servlet containers. There's practically no comparison to be made.

The only alternative language for .NET that's worth talking about is F#. But it's interesting to note that F# is sponsored and developed by Microsoft, while a language like Scala came from a third-party community. This is important because Microsoft is known to just dump projects that aren't generating a good return on investment.

     Java is now backed by Oracle which isn't the most friendly
     of open source companies either
Java was always backed by a huge community that includes IBM, Google, Intel, Red Hat, Novell and the Apache Foundation. The recent lawsuit against Google only goes to show that the genie is out of the bottle.


Actually Nemerle is a language worth talking about. What those guys are doing is brilliant. I remember even years ago - playing with the language - I was able to embed a natural syntax for a constraint programming library, encode a clean syntax on top of/for phantom types and write a monad system with transformers (via the macro system - think Lisp not C). This was back before Monads were cool in the regular community but very old hat in the haskell community.

What they are doing with version 2 is even more impressive and they are now sponsored by JetBrains.

F# is an excellent language* if you are doing Windows programming right now, you would be doing yourself a disservice not to look at it due to preoptimized worries. The language source is available , hacked on and ahead of Microsoft's version in certain aspects. In the case of a dumping, the community is talented and F# generates IL, unless C# is abandoned there is really nothing to worry about.

* I'm experimenting with intelligent, data based programming with a mix of Steffen Forkmann's JSON schema inference + ConceptNet + custom query syntax + embedded prolog + machine learning.


"unless C# is abandoned there is really nothing to worry about."

Hasn't Microsoft abandoned C# for internal development?


No. There was some FUD that because they didn't do things like rewriting Office in C# they weren't committed. Because rewriting 100 million lines of C++ just to prove a point would be such a good investment in time.


Major Products written substantially in C#:

SharePoint Visual Studio 2010 The C# compiler is being rewritten in C# Microsoft Dynamics


> it's not true for Java the VM, which evolved a lot faster than the CLR.

I'm curious as to what you mean by this; what part of the Java VM has evolved faster?


Compare the choice of garbage collectors and high performance JVM implementations vs what the CLR offers. I don't have recent benchmarks for the MS implementation of the CLR, but Mono lags noticeably behind Java in terms of performance: http://shootout.alioth.debian.org/u64q/benchmark.php?test=al...


Mono is a completely different implementation from the CLR. I wouldn't be so quick to discount the CLR's GC.


I find it interesting that when folks want to claim that .NET is crossplatform they cite Mono, but when performance is discussed they are only prepared to discuss the performance of the MS Windows implementation provided by MS. The last time I benchmarked .NET (on Windows) it was significantly slower than java (with -server flag), but their EULA forbid publishing benchmark results at the time (was a long time ago, but since the parent is discussing .NET v1.0 and it's creation...)


Microsoft disallows .NET benchmarks. If you want to compare Java and .NET you have to have their approval first.


Can you cite that? It sounds like an odd thing to do/suggest. I'm not even sure how Microsoft would stop people even if they wanted to...


I was wrong, they changed the EULA in the meantime.

Here are the conditions I could find (you still have to comply with those conditions, but it's doable): http://msdn.microsoft.com/en-us/library/ms973265.aspx

And they can stop you through the EULA .NET gets distributed under, because no other license gives you the right to use .NET, so you have to do it under the terms of that license.


The MS CLR is also significantly slower than Java: http://shootout.alioth.debian.org/demo/benchmark.php?test=al...


The situation is a bit more complicated than that.

To begin with, there are basically three CLR backends with sufficient maturity: Microsoft.NET, Mono, and Mono/LLVM, of which Mono is probably the slowest, because it is optimized for fast startup; that is matched against java -server, which (unlike -client) does extra optimizations (like mono --llvm) at the expense of a slower startup time. This works for short benchmarks, because compilation time is fairly minimal for these tight-inner-loop benchmarks.

Second, none of these benchmarks really touch on any of the JVM's weak spots. The classical example here is complex number arithmetic, where even an addition can potentially trigger a heap allocation (the JVM escape analysis only helps as long as functions don't actually store results anywhere). There are unfortunately quite a few such features where you get extra overhead to implement the workarounds.

Third, the JVM tends to be a bit of a memory guzzler. Combined with java -server's fixed-size heap, that can create problems in some scenarios (such as a system process that forks a few instances of itself).

In the end, few people will pick one or the other for the raw performance (which, in the end, is not very far apart), but for the ecosystem that comes with each backend.

It is worth noting that there are also other VM-related concerns, and overall, the CLR has the superior design as a general-purpose language backend.

For example, CLR functionality is a superset of JVM functionality. That makes the CLR far more pleasant to use as a compiler backend than the JVM (the reason why JVM-based languages use the JVM primarily is probably to hook into the JVM ecosystem, not because they love devising workarounds to implement closures or value types somewhat efficiently).

Similarly, P/Invoke is in practically all aspects superior to JNI. If you need to call native code a lot, P/Invoke is almost always the easier way to do it.

Library versioning is another issue where the CLR has the better story.

Again, what will almost certainly drive the use of one or the other for 99% of all programmers is what infrastructure (frameworks, libraries) they need. If they want to develop web applications using Scala/Lift, the JVM it is. If you want to implement games using Unity, you're going to use the CLR. The benefits you get from reusing code will almost certainly dwarf other concerns.


>>Mono/LLVM<< What a pain! -- "configure: error: Compiling with stock LLVM is not supported, please use the Mono LLVM repo at https://github.com/mono/llvm, with the GIT branch which matches this version of mono, i.e. 'mono-2-10' for Mono 2.10."


Not to mention that an out of the box .NET/CLR will just run fairly well. An out of the box JVM may or may choke, depending on the app you're trying to deploy. I've spent so much time trying to tune our JVMs for performance, that sometimes it's nice just to deploy a C# app on the CLR and have it just "work".


>>matched against java -server, which (unlike -client) does extra optimizations<< FWIW On a quad-core machine -server is the default.


      That makes the CLR far more pleasant to use as a 
      compiler backend than the JVM (the reason why 
      JVM-based languages use the JVM primarily is probably 
      to hook into the JVM ecosystem, not because they love
      devising workarounds to implement closures or value 
      types somewhat efficiently).
That's not true.

The JVM has inherent advantages that the CLR does not have ...

(1) the JVM can inline virtual method calls and with some machinery attached (bytecode manipulation) you can get pretty close to zero overhead when invoking functions dynamically (the reason for why dynamic languages on top of the JVM have been pretty awesome)

On implementing closures efficiently ... you should check your facts, because delegate invocation on top of .NET always has overhead compared to plain method invocation, while on top of the JVM you can make it so that those invocations will be inlined at runtime (in server mode the JVM even inlines method calls made through plain reflection, which is considered extremely expensive).

Implementations such as JRuby successfully do this, but because of JRuby's nature, the call-stack sometimes gets too tall, beyond the capabilities of the JVM to inline, however this is addressed with the InvokeDynamic support from JDK 7, which will make dynamic method calls as efficient as normal calls.

Because the CLR is not capable of such optimizations, compiler authors have to do a lot of optimizations ahead-of-time, while on the JVM even a dumb compiler can produce good results ... for proof, compile 2 binaries from the same source-code, one with the Mono compiler, the other one with the official compiler, then run on the same platform and compare.

(2) compiler writers love the Jar/.class formats, being really easy to produce binaries for Java, coupled with the maturity of libraries such as ASM ... contrast this with the CLR .dll format, for which the .pdb format for attaching debugging symbols is proprietary and NOT documented, which pushed the people working on Mono to come up with their own format (.mdb). Of course, through reverse engineering and through some code that Microsoft published, the interoperability of Mono with .pdb has improved lately, but it's still painful.

(3) there are many tools available for the JVM that complement compiler authors, tools like ObjectWeb's ASM, which have poor substitutes for .NET. And if you want a good parser generator, there are plenty to choose from for Java, while for .NET the only reasonable choice being Antlr, which is primarily a Java tool that also supports .NET -- on the other hand if you wanted to express your grammar with PEGs I personally couldn't find an option for .NET, while there are several available for the JVM.

(4) the features in .NET that are not used in C# have received poor support. For instance tail calls were NOT guaranteed to be eliminated, even if you specified this behavior in the compiled bytecode. But more than this, tail-calls have severe overhead over plain method calls. And did I mention that tail-call optimizations, as produced by the F# compiler, simply won't work reliably on Mono?

On value types, this is not such a huge issue as many people think. In Scala there's the proposal SIP 15 (value classes) which are implemented in the latest Scala 2.10 milestone release. This will open the door to much needed primitives, like unsigned ints: http://docs.scala-lang.org/sips/pending/value-classes.html

Also, you're making a mistake if you think the CLR type system doesn't have flaws. For instance the CLR lacks a Union type, which makes it a bitch to implement lazy languages, such as Haskell.

The CLR also lacks anything that would aid in implementing continuation-passing-style. The JVM doesn't have anything for it either, however for the JVM exceptions can be used for unwinding the call-stack efficiently and compared with the CLR, exceptions on the JVM are very, very cheap.

This coupled with bytecode manipulation and library authors have been able to build libraries that make use of CPS efficiently.

(5) P/Invoke is basically JNA ... https://github.com/twall/jna

C# does make it easier because it has a couple of primitives that make the marshaling of data easier.


First of all, let's clear some things up so we don't talk in circles. :)

Picking either the JVM or the CLR as your compiler backend is generally done because you either want (1) to hook into the respective infrastructure, (2) want a mature GC technology without having to develop your own, (3) need to generate reasonably fast code at runtime, or (4) all of the above. Most likely, it's a combination of (1) and (2).

If not, you're probably better off picking a more flexible backend that does not limit your object model or (if you're doing concurrency) your memory model. In short, either the JVM or the CLR forces you to compromise when emitting code.

In particular, both the JVM and the CLR are hostile environments for functional languages; if you're compiling a functional language to either (instead of, say, LLVM or MLRISC), you're almost certainly doing it for the JVM ecosystem, i.e. interoperability, vendor support, availability of tools, and such, not because it is a great fit for your needs.

So, when most of your points are about the superiority of the JVM ecosystem, I'm not disagreeing at all.

What I am still maintaining is that the CLR has the superior design. And yes, I am painfully aware of its limitations, too. Superiority is relative, not absolute.

All that said, there are a few of your points that merit a specific response.

(1) First of all, virtual call optimization (not just inlining) is something that the JVM does to solve what is primarily a JVM-specific problem. Simply put, while the CLR allows you to emit both call and callvirt instructions, the JVM has only invokevirtual (invokespecial, which was originally called invokenonvirtual, is more limited). The only option to tell the JVM that a call is non-virtual is to target a final method, which may require code duplication.

Second, nothing in Ecma 335 prohibits virtual call inlining when it can be proven to be safe. I don't know why Microsoft .NET/Mono still don't do it, but I suspect that they just don't see a big need for it, given that C# (unlike Java) uses non-virtual methods by default, and compilers for other languages with different semantics (e.g., Eiffel) can emit non-virtual calls at non-polymorphic call sites if they need to. Virtual call optimization could still be useful for inter-assembly calls (where the target assembly isn't strong-named), but that's probably a rare enough case that it's not certain whether the overhead is justified.

(2)/(3) Generating DLLs is pretty straightforward with ILASM or Mono.Cecil. Including debug information. You can only generate .mdb information on Windows, but then you only need it on Windows, too.

(4) The Ecma standard actually does make tail call elimination not optional for non-virtual calls. "CLI implementations are required to honor tail. call requests where caller and callee methods can be statically determined to lie in the same assembly; and where the caller is not in a synchronized region; and where caller and callee satisfy all conditions listed in the 'Verifiability' rules below."

With respect to value types, you probably misunderstood me. SIP-15 is about adding a layer of abstraction around existing value types. That's valuable, but it's also largely a compiler frontend, not backend issue.

What I was referring to was that the JVM makes heap allocations for tuple types all but necessary, especially if you're looking at collections of tuples. Sometimes you can work around it (such as mapping an array of N complex numbers to an array of 2*N real numbers) if you don't mind the extra work, but just as often that's not easily possible.


Just wanted to say thank you for this great list!


> And because Java the language does not evolve very fast, the ecosystem has given birth to mature JVM compilers/interpreters for languages such as Clojure, Scala, JRuby and Groovy, amongst others.

Most of those examples you gave (i.e. Clojure, Scala, JRuby) also have CLR versions.


...And most of the examples you give are more mature on the JVM and most developers in these languages target the JVM exclusively.


I'm curious: What are the breakages in backwards compatibility that C# introduced?

The only one I can think of is the upcoming change in closure semantics for loop variables - something that is being changed to make it work like people would expect for 99% of the case and risks breaking that 1% of cases where someone actually wants the same loop variable to be closed over in all functions defined within the loop.

(e.g. Linq is actually much like a monad with SelectMany as bind)

Correct me if I'm wrong but I believe, with SelectMany, Linq does in fact, provide everything required to support monads.

Agree with everything else you said - especially on flawed comparisons between the two languages.


The biggest breaking change was from version 1 to 2. Generics were added, as were partial classes, private setters on properties, and delegates. Along the way a lot of little breaking changes were also taken on. Things that change the behavior of the language enough to warrant calling out the incompatibility from previous versions (if you happened to rely on a particular behavior) but which generally result in an improvement in the language.


Did generics break working code?

I didn't think that was possible.


Not easily. There are a few rare instances where they could cause an error during compilation of code that compiled fine for .net 1.1, and some problems with using tlbimp. I don't think it caused previously compiled code to break though.


I don't think there was much backwards compatibility breaks, but in generics C# broke forward compatibility of the bytecode, whereas Java didn't (they used type erasure for their generics).

With that said, C# almost always breaks forward compatibility. It seems like a somewhat odd design goal to have for a language nowadays.


I'm sure you probably know this but that kind of breaking change (to the degree that we can agree that it even exists) is not a C# breaking change since generics aren't a purely-language-feature the way they are in Java. I don't have the C# spec handy but I'm pretty sure that it doesnt prescribe that generics are reified.


I'm not actually intimately familiar with C#. I remember reading about some of the changes between major .NET version (e.g. 2 to 3) which did not maintain backwards compatibility. However, on review, that may have been backwards compatibility of the bytecode rather than the language proper, in which case I am mistaken.

It would be great if somebody who knows one way or the other for sure chimed in.


C#3 adds features not found in C#2 (just as C#2 added features not found in C#1).

However the C# compiler will produce bytecode compatible with the .NET 2.0 runtime from C#3-code. And all C#2 code will compile just fine with the C#3 compiler.

Basically what breaks is trying to shove C#3 code through a C# 2 compiler, and that can hardly be said to surprise anyone.

The part which may be confusing is that unlike Java and the JVM, C# is tied to CLR runtimes, and you can have several installed at the same time.

C#1 was tied to the .NET 1.x runtime. C#2 was tied to the .NET 2.x runtime (which also powered .NET 3.0 and .NET 3.5). C#3 was tied to the .NET 2.x runtime with some extra LInQ DLLs installed (aka .NET 3.5). C#4 (which again adds more features) depends on the .NET 4.x runtime. And all these runtimes can be installed at the same time.

The fact that Microsoft is willing to "leave old runtimes behind" and create new ones when the language features being requested requires it means they can iterate much faster than the Java can, since backwards bytecode compatibility is not required.

I think it is a decision which has worked out largely to their advantage.


The breaking changes are usually minor and side-effects of the other language changes. For example, IEnumerable<T> was made covariant in .Net 4.0. Consider what happened to code passing an IEnumerable<List<int>> to a method with two overloads taking IEnumerable<Object> and Object respectively.


Interestingly, named parameters introduced a kind of breaking change:

http://blogs.msdn.com/b/ericlippert/archive/2011/11/07/break...


The only major breakage has not been in the language, but in the .NET framework libraries; and then mostly in ASP.NET. Between 1.1 and 2.0, quite a few things broke there. Upgrading was no fun at all.


There's no big mystery. I was there and saw it all happen. Java, the language, hadn't made any progress for several years. Programmers like me would complain at Java user group meetings here in Silicon Valley that we wanted certain features in Java (enums, lightweight structs, etc.) and the Java guys from Sun would always do an Apple on us: you don't know what you want. You're not experts; we are. We know what you really need better than you do, because we're smarter than you and have better taste, so trust us, you don't really need <whatever features we were asking for>.

There was always a Microsoft rep at those Java user group meetings. After the meeting, he would come up to us and ask us again what we wanted and why. Unlike anyone from Sun, he'd ask good questions, listen carefully, and take notes.

Microsoft genuinely liked the language as a much-better C++, but wanted it to have native extensions on Windows for two reasons: 1) it would encourage the writing of code that would be less portable, thereby reducing the threat to MS's advantage of being the platform with the most apps, and 2) apps written in "pure" Java just weren't as good as native apps, and for Java to become the "better C++" that many programmers inside MS were hoping for (for their own use), it was becoming clear they would need native extensions.

MS tried to produce a Java with native extensions: Visual J++. Sun filed suit, claiming that MS was in violation of contract. I don't recall the outcome of the suit, because MS decided that Java's slowing acceptance as a general-purpose language for client-side apps (and part of that slowness was CAUSED by MS) meant that there was less need to claim support for Java. It wasn't going to take over the world after all. (Steve Jobs later made the same decision). Not supporting it wouldn't mean you would be left behind. They abandoned Visual J++.

Instead, MS wanted their own Java-like language with no strings attached. Java was a trademarked name, so they created a language with a different name but all the Java features (LANGUAGE features) that programmers liked so much, plus most of the features that Java programmers were begging for that Sun was sniffing at, plus the native extensions needed to write Windows apps that would only run on Windows AND would run so well they would, in general, be indistinguishable from native apps written in Visual C(++), and they built a great app builder (VC#) to go with it.

They did a beautiful job with Visual C# and rattled Sun so much that suddenly a lot of those "impossible" features we "didn't need anyway" became available in Java, but C# has always maintained a healthy lead over Java in nice features for client-side programming, IMO.

(And Java has maintained a superior overall adoption rate, especially on servers.)


> MS tried to produce a Java with native extensions:

Not exactly. MS replaced Java's existing native extension functionality (JNI) with Windows-only native hooks called JDirect. Interestingly, it did so with these @com (EDIT: sorry, I meant @dll -- @com was related to COM hooks, which stuck around well into the .NET era as attribute hooks) method macros that, in a way, prefigured attributes in both Java and C#.

EDIT: If by 'native extentions', you meant the WFC classes, then yes these were additions, not a replacement of JNI. But these were not part of the lawsuit per se. The suit focused on the removal of JNI and the corruption of the JVM spec with the JDirect functionality.

> plus most of the features that Java programmers were begging for that Sun was sniffing at

'most of the features' is pushing it a bit. The only feature that J++ had over Java 1.1 at the time was method pointers (delegate). The lawsuit focused on those corruptions of Java: removing JNI as well adding JDirect and the 'delegate' keyword. This, according to the court, violated the terms of the license agreement signed by MS.

Microsoft lost the lawsuit, and that was the reason they ditched J++ and began work on C#/.NET, which built upon many of the lessons learned in J++. I think otherwise MS's plan was to continue building an ecosystem around the MSVM not unlike what they eventually built around the CLR.

It would be interesting to compare the MS/Sun ruling with the recent Google/Oracle ruling, as the terms of the two suits were rather similar.

It would seem to me that the biggest difference there is that MS did in fact sign a license agreement with Sun that constrained the nature of their use of Java. Google did not sign any agreement, and I wonder if this was a calculated move on Google's part, in light of the MS/Sun J++ ruling.

Considering the recent ruling by Judge Alsup regarding Android's similar adoption/modification of the Java spec, does this mean Microsoft would have stood a better chance by simply implementing J++ with no agreement from Sun at all? On my naive, IANAL reading of it, that would seem to be the case.


>> plus most of the features that Java programmers were begging for that Sun was sniffing at...

>'most of the features' is pushing it a bit. The only feature that J++ had over Java 1.1 at the time...

The Java-like language with no strings attached with the different name and all the new features that I was talking about was C#, not Visual J++.

> Microsoft lost the lawsuit, and that was the reason they ditched J++...

But that's not the reason they created C#. They created C# because they wanted a Java-like language (Java-like as in having the Java advantages over C++, not as in Java-compatible) without any limits on how good they could make it for writing Windows client apps. When they created J++, they were still assuming that Java was a juggernaut that they would have to cooperate with to some extent or get left behind, like HTML. Over time, it became clear that Java was fizzling out on the client anyway, so the need to cooperate was fading fast while the desire to create something that would actually be great for Windows clients was becoming overwhelming. After Java failed as a client-side alternative for creating Windows apps, MS was going to fork their own language, regardless of its name.


I think the differences between C# 1 and the current Java have been rather downplayed by this discussion. C#1 was different from Java in a number of very important ways:

* You could call directly into the Win32 API (the issue that caused Sun and Microsoft to fall out)

* Value Types. Still being discussed for v8.

* Lambdas. Yes, in version 1.

* C# had enums, Java didn't. (Now Java has better enums than C#)

* A syntactic sugar for property get/set. This was extremely important to Microsoft.

* A decent COM-interop story. Again, very important to Microsoft.

Of course it was similar. It was a design goal to be able to run Java code on the .NET VM. But it was far from identical.

Jon Skeet could no doubt expand this list


I'm not so sure about lambdas in v1. I remember v2 added anonymous functions ( ex " delegate(int x){...}" ), and real lambdas not coming until v3.


You're correct, anonymous functions were 2.0, sorry. However, I'd argue that, syntax aside, they were already true lambdas. The expression stuff that V3 added isn't even on the Java roadmap.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: