C# and Java are both nice languages. They reach similar goals through similar means, although C# adds some syntactical touches to Java, such as the foreach keyword and a more pleasant extends/implements construct. Unfortunately, the improvements are outweighed by the instances of regression. In this article, I'll compare the languages, while trying to avoid dipping to the JVM and CLR level.
Java: All the greatness of C++ without the hassle
When comparing Java and C#, you can’t help but notice their profound similarity, which is owed in part to their common ancestors, C and C++. However, when Gosling and crew sat down to create Java, they not only borrowed capabilities from C++, but more important, they pared away some of the unnecessary features that made C++ bug prone and difficult to learn. C#'s designers added a lot of the same C++ features that Java added but failed to remove some of the worst features of C++. The result was a language that still tries to be all things for all people and ends up internally conflicted and overly complicated.
Sloppy syntax pitfalls
The easiest errors to pick out are those in flow control and syntax. C# offers the goto command as a mechanism for altering the point of program execution. Computer scientists have been railing against this approach since Edsger W. Dijkstra published "Go To Statement Considered Harmful" in 1968. Goto invites code that is difficult to debug and hard to run through test coverage tools.
On a different note, operator overloading is the same sort of sin on a more subtle level. When '+' can mean anything depending on the type of the operands, the functioning of code is no longer transparent and unintentional side effects abound.
C# weaker security
C# contains a simple mechanism for marking regions of code as unsafe. Within these unsafe regions, the safeguards put in place by Java and later C# to prevent programmers from directly altering memory locations and using point arithmetic are suspect. Dropping to the memory address level when working with a garbage-collected high-level language muddies the intentional object/memory separation. Bugs can slip in easily, debugging is a nightmare, and buffer overruns once again rear their ugly heads to open the security holes for which C and C++ are famous.
C# also allows simple access to native libraries on the host system. This binding to non-.NET objects is similar to the functionality offered by the Java Native Interface (JNI), but it's more insidious. Whereas JNI was designed to carefully limit the interaction between Java code and native code with well-defined interfaces, .NET makes calling off to native object files so invitingly simple that developers can do so without realizing they're throwing platform portability out the window in the process.
C#, and to a greater extent .NET, has tightly integrated SOAP Web services. SOAP is a fine standard for making remote procedure calls using XML to specify parameters and results values, but it is by no means the only way. Using external libraries for Web services allows Java developers to easily change out their Web service flavor, be it SOAP, XML-RPC, or something as of yet uninvented. C# developers, of course, always have the option of using external libraries for non-SOAP Web services, but the tight integration of the SOAP standard is more limiting than it is empowering.
The owner is scary
Perhaps the scariest feature in C# is its owner. Microsoft has made a good show of opening up C# and .NET for non-Windows platforms, but it is largely all show. Its CLRs for non-Windows platforms are problematic and buggy. It has run C# through the ECMA standardization process—a step Sun is still too cowardly to take with Java. The fear comes in with the level of lockdown Microsoft could apply given the desire to do so. Microsoft has filed for a patent that would preclude the writing of third-party CLRs, such as the Mono project. If Microsoft decided to clamp down on the free C# and .NET communities, it wields enough financial and legal muscle to chase development back into Win32 platforms—a not unfavorable outcome from its position.
The Java language, on the other hand, isn't an ECMA standard, and shame on Sun for that. It is, however, implementable without patent encumbrance, and both its virtual machine and core class libraries are available in both open and closed source implementations from third parties. C# looks free but isn't really, while Java looks restricted but can legally be implemented freely.
Finally, and I never though I'd get to say this, but Java has better tool support even when integrated development environments (IDEs) are taken into account. Visual Studio .NET is a fine IDE. It represents years of work and is feature rich. However, the Eclipse IDE, which includes Java support, has pulled ahead of Visual Studio in stability, ease of use, and offered features. IBM contributed significantly to Eclipse, and if you buy into the old software development adage "Build one to throw away," you can count Visual Age as its first, now discarded, attempt. Fortunately for the C# folks, a .NET version of Eclipse is in the works.
Not so bad, but not Java
Appraised honestly, there's really nothing that terrible in C#. It isn't the horror that Visual Basic is, and it doesn't practically insist that developers shoot themselves in the foot like C does. The bottom line, however, is that C# doesn't do much, if anything, better than Java, and it's measurably worse in a few areas. When selecting between these two largely identical languages, pick the one that's slightly better and battle-tested: Java.