C SharpとJavaの比較

オブジェクトの扱い

いずれの言語もオブジェクト指向言語であり、その文法はC++に類似しているが、C++との互換性はない。メモリ再利用の手段として、従来の手動で解放する方法ではなくガベージコレクションを使用する。また、スレッド同期の手段を言語構文に組み込んでいる。
いずれの言語も強い参照と弱い参照の両方をもつ。Javaでは参照がガベージコレクタによって回収された時に通知を受けるリスナーを登録することができる。これはWeakHashMapの性能を考慮したものである。C#にはこれに相当する機能はなく、ファイナライザ（Javaにも存在する）を使用する方法しかない。その一方、C#は指定したオブジェクトのファイナライザ呼び出しをプログラマが抑止することができる。「世代」の概念をもつガベージコレクション（Javaと.NETいずれにも当てはまる）においてファイナライザの呼び出しは性能に大きな影響を与えるため、またファイナライザはプログラマがオブジェクトを破棄しなかった場合のフェイルセーフであるため、これは非常に有効である。ファイナライザをもつオブジェクトは通常余分な世代が与えられ、回収されるまでに長くかかる。
C#は、一部の言語設計者から危険であると指摘されるポインタを使用した演算が制限つきながら利用できる。C#はポインタを使用するコードブロックあるいはメソッドをunsafeキーワードで修飾することでこの懸念に対応している。これにより、このコードを利用する者はそれが他の部分に比べて危険であるということを知ることができる。また、このようなコードをコンパイルする際にはコンパイラに対して/unsafeスイッチを指定する必要がある。一般に、unsafeコードが使われるのはアンマネージなAPIやシステムコールとの相互運用が必要な時、あるいは性能の向上が必要な時のみである。

配列

C#およびJavaの配列は参照型であり、第一級オブジェクトである。C#の配列は共通の基本クラスSystem.Arrayから派生するが、Javaの配列はjava.lang.Objectから派生する。代わりに、配列のユーティリティクラスとしてjava.util.Arraysが用意されている。
いずれの言語も配列は共変性 (covariance) を持つ。
System.Console.WriteLine(typeof(int[]).BaseType); // System.Arrayobject[] objs = new string[3]; // 代入可能。objs[0] = "test";objs[0] = 10; // System.ArrayTypeMismatchException
System.out.println(int[].class.getSuperclass()); // java.lang.ObjectObject[] objs = new String[3]; // 代入可能。objs[0] = "test";objs[0] = 10; // java.lang.ArrayStoreException
なお、Java 1.5で拡張for文がサポートされた。これはC#のforeach文に相当する。配列を始めとするコレクション型を、イテレータにより走査することができる。
C#は真の多次元配列（矩形配列）をサポートするが、Javaはサポートしない。C#およびJavaはともに「配列の配列」をサポートする（C#ではジャグ配列と呼ばれる）。

内部クラス

いずれの言語もネストされた型（入れ子にされた型：クラスや構造体のブロックの中で定義されたクラス・構造体・列挙型・インターフェイス）をサポートする。Javaではインターフェイス内部にクラスや列挙型を定義することもできる。C#ではインターフェイス内部にクラス・構造体・列挙型・インターフェイスを定義することはできない。
Javaでは、ネストされたクラス (nested class) は既定で内部クラス (inner class) となる。内部クラスは外側のクラスのインスタンスを暗黙的にキャプチャすることで、静的メンバ、非静的メンバいずれにもアクセスすることができる。ネストされたクラスがstatic修飾されていた場合は静的メンバのみにアクセスできる。メソッドの内部にクラスを定義することもでき、これはローカルクラス (local class) と呼ばれる。ローカルクラスでは、外側のローカル変数には読み取りアクセスのみできる。また、型の名前を持たないローカルクラス（匿名クラス: anonymous class）を定義し、同時にインスタンス生成をすることもできる。
C#では、ネストされたクラス／構造体から外側のクラス／構造体の非静的メンバにアクセスするためには外側のクラス／構造体のインスタンスへの明示的な参照が必要になる。Javaの内部クラスやローカルクラスに相当する機能は存在しない。代わりに、C# 2.0以降では匿名メソッド (anonymous method) が、C# 3.0以降ではラムダ式が、そしてC# 7.0以降ではローカル関数がサポートされ、外側の変数をキャプチャするクロージャとして利用できる。なお、C# 3.0では限定的なローカルクラスとして、匿名型（読み取り専用プロパティのみを持つ、匿名のクラス型）がサポートされる。

ジェネリクス

Javaではジェネリクスは型消去 (type erasure) によって実装されている。これによってジェネリック型についての情報は実行時には失われ、リフレクションを通してのみ取得できるようになる。.NET 2.0では、ジェネリック型についての情報は完全に保存される。Javaはプリミティブ型に対するジェネリック型は定義できないが、C#では参照型・値型（プリミティブ型を含む）いずれに対してもジェネリック型を定義できる。Javaはその代わりにボックス化した型を使用することができる（List<int>の代わりにList<Integer>など）が、全ての値をヒープに確保し直す必要があるため、パフォーマンスコストが高い。JavaとC#はいずれも、参照型に特殊化されたジェネリック型は、型によらず共通のコードが実行される。しかし、C#において値型に特殊化された場合、CLRは型に最適化されたコードを動的に生成する。.NETにおいては、ジェネリック型に対する型安全性はコンパイル時にチェックされ、CLRにロードされる時に強制される。Javaにおいてはコンパイル時に部分的にチェックされるのみであり、Java VMは実行時にジェネリック型に関する情報を持たないため、キャスト操作を行う必要がある。

ジェネリクスの型制約

C#、Java共に、ジェネリクスの型制約を持つ。C#では基底型指定以外に、class(参照型)、struct(値型)、new()(デフォルトコンストラクタ有)の各制約を指定できる。
// C#class ConstraintClass { }class SomeGenericsClass<T> where T : ConstraintClass { }class OtherGenericsClass<T> where T : new() { }
// Javaclass ConstraintClass { }class SomeGenericsClass<T extends ConstraintClass> { }

ジェネリクスの共変性と反変性

C#、Java共に、ジェネリクスの共変性と反変性 (covariance and contravariance) を持つ。ただしC#における共変性・反変性のサポートはバージョン4.0以降である。
C#では、型定義側で共変性outあるいは反変性inを指定し、利用側では変性の指定を行わない。なお、C#の値型は不変 (invariant) であり、共変性・反変性は適用されない。
// C#// System.Func<T, TResult> と同様のデリゲートを定義。// 型定義側で変性の指定を行う。public delegate TOut MyFunc<in TIn, out TOut>(TIn arg);public class GenericsTest {
    public static void Main() {
        // 利用側では変性の指定を行わない。
        MyFunc<object, string> func1 = x => "<" + x + ">";
        MyFunc<string, object> func2 = func1;
        object ret = func2("test");
        System.Console.WriteLine(ret);
        System.Console.WriteLine(ret.GetType()); // System.String
    }}
Javaでは、型定義側では変性の指定を行わず、利用側で共変性extendsあるいは反変性superを指定する。
Javaではさらに、上限と下限のいずれも指定しないワイルドカード?による型引数の指定が可能である。以下にJava 8の例を示す。
// Java// java.util.function.Function<T, R> と同様のインターフェイスを定義。// 型定義側では変性の指定を行わない。@FunctionalInterfaceinterface MyFunc<TIn, TOut> {
    TOut apply(TIn arg);}public class GenericsTest {
    public static void main(String[] args) {
        // 利用側で変性の指定を行う。
        MyFunc<? super Object, ? extends String> func1 = x -> "<" + x + ">";
        MyFunc<? super String, ? extends Object> func2 = func1;
        Object ret = func2.apply("test");
        System.out.println(ret);
        System.out.println(ret.getClass().getName()); // java.lang.String

        // 変性の上下限いずれも指定しないこともできる。
        java.util.List<?> list;
    }}

型推論

C# 3.0でコンテキストキーワードvarによる限定された型推論が導入された。ローカル変数の宣言時に、型を右辺から推論できる。メソッド引数やフィールドには使えない。また、ラムダ式の戻り値および仮引数は型推論により決定される。ラムダ式の仮引数は型を省略することで型推論されるが、型推論が困難な場合には明示的に型を指定する。
Java 7で導入されたダイヤモンド演算子<>は宣言文の右辺ジェネリクスの型を省略できる程度のものでしかなかったが、Java 10ではC#同様に予約型名varによるローカル変数の型推論が導入された。Java 8で導入されたラムダ式では、仮引数の型を省略することで型推論されるが、さらにJava 11ではラムダ式の仮引数の型にvarを使用して型推論できるようになった。

表記法と特殊な仕様

Java 1.5で、あるクラスの静的メソッド・静的フィールドを短い名前で使用するためにstatic import構文が導入された。これにより、例えばClassAクラスのfoo()メソッドを静的インポートして、無関係のClassBクラスにてClassA.foo()のように型名で修飾することなく、foo()を直接使用できる。C# 6.0でも同様の機能としてstatic usingディレクティブがサポートされた。
C#は静的クラス（Javaの静的内部クラスとは異なる）の構文をもち、これによってクラスは静的メソッドのみを持つことができるようになる。C# 3.0から、型に静的にメソッドを追加するための拡張メソッドが導入されている（Foo型のインスタンスfooについての処理を行うBar()拡張メソッドを定義することで、あたかもFooにインスタンスメソッドが追加されたかのように、foo.Bar()と記述できる）。この機能は、しばしばオブジェクト指向におけるカプセル化の概念を破壊するものとして誤解されるが、ターゲットとなるクラスのプライベートメンバにアクセスすることはできないため、そのような事は無い。

イベント処理

Javaでは、Observer パターンの記述を容易にするために匿名クラスという糖衣構文が用意されている。匿名クラス構文により、クラス本体の定義とインスタンスの生成を同時に行なうことができ、また定義と生成がひとつの式として扱えるため、インターフェイス実装クラスのインスタンスを一度限りしか生成しない場面、例えば特定のインターフェイス実装（あるいは特定のスーパークラスのメソッドのオーバーライド）を要求するイベントハンドラーの設定時などによく用いられる。Java 8ではラムダ式がサポートされ、匿名クラスの代わりに使うこともできる。Java 7まではC#のデリゲートに相当するものは存在しなかったが、Java 8では類似機能としてメソッド参照がサポートされるようになった。
C#ではデリゲート (delegate) 型をはじめ、イベント処理をサポートするための機能が広範囲に渡って言語レベルでサポートされている。デリゲート型はメソッドへの型安全な参照であり、複数のデリゲートを結合してマルチキャスティングすることもできる。デリゲートを結合／分離するための+, -演算子、イベントハンドラーをカプセル化するためのイベント構文 (event, add/remove) や、イベントハンドラーを登録／削除するための+=, -=演算子が用意されている。デリゲートは共変性と反変性 (covariance and contravariance) をサポートし、完全なクロージャとしての性質をもつ匿名関数（匿名メソッドおよびラムダ式）を作成することができる。

非同期処理

JavaおよびC#はともに標準ライブラリでスレッドをサポートしている。OSのスレッドに対する薄い抽象化を提供するクラスとして、Javaにはjava.lang.Thread、C# (.NET) にはSystem.Threading.Threadが用意されている。そのほか、セマフォなどの同期オブジェクトやアトミック演算なども標準ライブラリでサポートされている。
言語組み込みの同期構文として、Javaにはsyncronizedブロックおよびsyncronizedメソッドが用意されている。C#にはlockステートメントが用意されている。
C# 5.0以降はasync/awaitコンテキストキーワードによる非同期メソッド構文がサポートされた。async/awaitはイテレータおよび.NET 4以降で追加されたタスク並列ライブラリ (Task Parallel Library; TPL) を実行基盤とする糖衣構文であり、非同期タスクの完了待機と実行結果の取得をあたかも同期メソッドのように記述することができる。

演算子多重定義

Javaは言語仕様をシンプルに保つため、また乱用による難読化を防ぐため、ユーザー定義の演算子オーバーロードをサポートしていない。一方でコードの書きやすさおよび直感性は犠牲になっている。
var myList = new java.util.ArrayList<Integer>(java.util.Collections.nCopies(10, 0));myList.set(4, -100);var gravityTable = new java.util.HashMap<String, Double>();gravityTable.put("Mercury", 0.377);gravityTable.put("Venus", 0.9);gravityTable.put("Earth", 1.0);double gravityOnVenus = gravityTable.get("Venus");
var b1 = new java.math.BigInteger(Long.toString(Long.MAX_VALUE));var b2 = new java.math.BigInteger(Integer.toString(1));var b3 = b1.add(b2);
C#は表記法の多くの点でJavaよりも多機能である。演算子多重定義やユーザー定義キャストなど、それらの多くはC++プログラマによって既に親しまれているものである。
C#はインデクサ（C++のoperator[]に相当）をサポートする。インデクサの定義構文はプロパティと似ているが、this[]という名前をもち、一つ以上の引数（インデックス）を持つ点が異なる。インデクサを定義する際、インデックスには任意の型を使用することができる。
var myList = new System.Collections.Generic.List<int>(new int[10]);myList[4] = -100;var gravityTable = new System.Collections.Generic.Dictionary<string, double>();gravityTable["Mercury"] = 0.377;gravityTable["Venus"] = 0.9;gravityTable["Earth"] = 1.0;double gravityOnVenus = gravityTable["Venus"];
C#は（論理的一貫性を保つための制限はあるものの）ユーザー定義の演算子オーバーロードをサポートしており、注意深く使用すれば簡潔で可読性の高いコードを記述することができる。
var b1 = new System.Numerics.BigInteger(long.MaxValue);var b2 = new System.Numerics.BigInteger(1);var b3 = b1 + b2;
また、C#では明示的メンバ実装 (Explicit Member Implementation) が可能である。これによって、インターフェイスメソッドの実装とクラス自身のメソッドの実装とを分離することができ、また同じシグネチャ（名前および引数の数と型の順序）を持つメソッドが異なるインターフェイスにそれぞれ存在した場合に、インターフェイス名を指定して区別することでそれらの実装を別々に行うことができる。

プリプロセッサ

JavaはC/C++でしばしば問題を引き起こしていたプリプロセッサを採用しなかった。一方でC#は限定的にプリプロセッサディレクティブを使用可能である。

条件付きコンパイル

Javaではプリプロセッサがサポートされないため、コンパイル時の分岐は不可能となっている。
一方、C#ではプリプロセッサディレクティブを用いた条件付きコンパイル (条件コンパイル、英: conditional compilation) が実装されている。また、指定されたコンパイル定数が定義されている時のみ呼び出されるようConditional属性をメソッドに指定することができる。この方法によって、DEBUG定数が定義されている時のみ評価される表明（アサート）機能 (System.Diagnostics.Debug.Assert()メソッド) が提供されている。Java 1.4からは実行時に有効・無効を切り替えられるassert文が言語仕様として導入されている。

名前空間とソースファイル

C#の名前空間はC++のそれと類似している。Javaのパッケージとは異なり、名前空間はソースファイルの物理的位置とは無関係である。Javaではパッケージ構造とソースファイルの位置が必ずしも一致している必要はないものの、デフォルトではそのような振る舞いをする。
Javaではアクセスレベルがpublicのクラス名はソースファイル名 (*.java) と一致していなければならない。つまり1つのソースファイルにpublicクラスは1つだけしか定義できない。デフォルトのアクセスレベルのクラスであれば、1つのソースファイルに複数のクラスをまとめて記述することができる。
C#ではそのような制約はなく、クラス名と無関係の1つのソースファイル (*.cs) に任意のクラスを複数記述することができる。C# 2.0からはpartialキーワードによってクラスの定義を複数のファイルに分けて記述することが可能になった。

例外処理

Javaは非チェック例外 (unchecked exception) に加えてチェック済み例外 (checked exception) をサポートする。C#では非チェック例外のみである。チェック済み例外は、プログラマがメソッドから発生し得るものを全て宣言し、捕捉する必要がある。
全てのエラーが処理されることを保証できるため、チェック済み例外は非常に便利だとする者もいる。一方、C#の設計者であるアンダース・ヘルスバーグのように、Javaのチェック済み例外はある程度実験的な仕様であり、小さなプログラムでの例を除いては実装する価値を見出せなかった、とする者もいる。一つの批判として、チェック済み例外はプログラマが空のcatchブロックを記述するのを促進し、catch (Exception e) {}のような危険なコードを増やす結果になってしまったというものがある。また別の批判として、メソッドの実装に変更を加えた結果新しいチェック済み例外が発生するようになる可能性があり、これによって契約が破壊されてしまう、というものがある。これは限られた例外のみが宣言されたインタフェースを実装するメソッドや、メソッドの内部実装が変更された場合に起こり得る。中には、このような予期しない例外が発生することを見越し、あらゆる型の例外が発生し得る、と宣言 (throws Exception) するプログラマもいる。これはチェック済み例外の利点を無にしている。しかしながら、いくつかの場面では例外連鎖 (exception chaining)、すなわち捕捉した例外を別の例外でラップして投げ直す、という手法が適用できる。例えば、ファイルにアクセスするコードがデータベースにアクセスするよう変更された場合、呼び出し側は内部で何が行われているかを知る必要がないため、SQLExceptionが捕捉された場合でもIOExceptionとして投げ直すことが可能である。
try-finallyステートメントにおいても両者は異なる。finallyは例えtryブロック内でthrowやreturnが実行された場合でも必ず実行される。これは、try内とfinally内で異なる値がreturnされた場合に予期しない振る舞いを生じることがある。C#ではfinallyブロック内ではreturnやbreakといった文の実行を禁止している。

低レベルコード

C/C++などで書かれたネイティブコード資産の再利用や、オペレーティングシステムあるいはハードウェアへのローレベルなアクセスを可能とするため、JavaおよびC#はともにネイティブ相互運用のための機能を提供している。
Java Native Interface (JNI) ではJavaコード内で非Javaコードをnativeメソッドとして呼び出すことができる。しかしながら、JNIは呼び出されるコードのインターフェイス（シグネチャ）に制限がある。このため、Javaと既存のネイティブコード資産との間に余分なレイヤーが必要になることがよくある。このレイヤーはJavaではない言語で書かれる必要があり、CやC++がよく用いられる。JNIを利用することで、逆にC/C++側からJavaのクラスライブラリにアクセスすることも可能である。
.NETのプラットフォーム呼び出し（Platform Invoke、P/Invoke）はC#からアンマネージコードの呼び出しを可能にする。プログラマはメタデータを通して、メソッドの引数や戻り値がどのように橋渡し（マーシャリング）されるかを完全に制御することができる。このため、既存コードのインターフェイスがC言語形式関数でありさえすれば、余分なレイヤーは必要にならない。P/Invokeは（Win32やPOSIXなどの）手続き型APIにはほぼ完全にアクセスすることができるが、C++クラスライブラリへの直接的なアクセスはきわめて困難である。そのほか、C++/CLI言語を介することで、C#とC/C++間の相互運用を行なうことも可能である。またCOM相互運用により、コード資産を相互に利用することも可能である。
C#は通常の型チェックなどのCLRの安全のための機能を無効にし、ポインタ変数を利用することができる。この時、プログラマはコードをunsafeキーワードでマークする必要がある。JNI、P/Invoke、unsafeコードはどれも同様に「危険な」機能であり、セキュリティホールやアプリケーションの不安定性につながる恐れがある。unsafeコードがP/InvokeやJNIに対して優れている点は、アンマネージコードを呼び出すことなくC#の機能内でタスクを完結できる点である。unsafeコードを含むアセンブリはコンパイル時にそのように明示的に指定する必要がある。これにより、実行環境は危険な可能性があるコードであるということを実行する前に知ることができる。

統合言語クエリ

C#には、統合言語クエリの為の拡張されたキーワード群が存在し、ソースコード上でSQL構文に似たクエリを直接記述することができる。このキーワード群は構文糖として機能し、決められた拡張メソッド呼び出しに展開される。
// 数列から偶数を抜き出して表示するvar inputs = new[] { 1, 5, 2, 3, 4, 7, 11, 10, 6 };#if true// LINQクエリ構文var results =
    from inputValue in inputs
    where (inputValue % 2) == 0
    select inputValue;#else// LINQメソッド構文var results = inputs.
        Where(inputValue => (inputValue % 2) == 0).
        Select(inputValue => inputValue);#endifforeach (var resultValue in results){
    Console.WriteLine(resultValue);}
統合言語クエリの拡張メソッド群は、コレクションに対するさまざまな集合演算が実装されており、このような演算をロジックで記述することなく、簡単に操作することができる。
また、ラムダ式から式ツリー（Expressionクラス）のインスタンスを生成することが出来るため、この機能を使用してデータベースへのクエリ発行・収集を、タイプセーフ性を失うことなく直接的に行うことができる（LINQ to SQL、LINQ to Entitiesなど）。
Javaでは、このようなクエリ構文を直接サポートしない。Java8ではC#のLINQメソッド構文に近い記述が可能となるStream APIとラムダ式がサポートされた。

JVMとCLR

Javaはまったく異なる多くのオペレーティングシステム間で実行できる。またパーソナル・コンピュータに限らず、高度な計算処理や制御を必要とする家電製品や、Blu-ray Discのインタラクティブ技術にもBD-Jとして使用されている。このように数多くのJava仮想マシン (Java VM, JVM) 実装が存在する。
C#および.NETテクノロジーもやはりクロスプラットフォームである。.NET Frameworkはマイクロソフトによる.NETの実装であり、共通言語ランタイム (CLR) はマイクロソフトによる共通言語基盤 (CLI) の実装である。主なプラットフォームはWindowsだが、他のプラットフォームにも実装が存在する。有名なものにMonoがある。ただし、マイクロソフトによる実装と比較して未実装部分が多く、利用できるライブラリに大きく制限がある。マイクロソフトによるモバイル／組み込み環境向け実装としては.NET Compact Frameworkがある。
2017年現在、.NET Frameworkの他に.NET CoreやMono/Xamarinなどの実装が存在し、多くのオペレーティングシステム向けの開発が可能となっている。

標準

両言語の構文（文法）、プログラミングインタフェース、バイナリ形式（実行ファイル形式）、実行環境などは様々な機関によって管理されている。
C#はEcmaとISO、JISによって定義されている。標準化の対象は言語構文、基本クラスライブラリ、アセンブリ形式、実行環境（共通言語基盤: CLI）など多岐に渡る。下位層フレームワークの上に新しく実装された上位層ライブラリの多くはこの標準には含まれない（Windows Forms、ASP.NET、ADO.NETなど）。
現在のところ、Javaのどの部分も第三者の標準化団体によって標準化されていない。Javaの商標、ソースコードやその他の素材に関してはオラクル（旧サン・マイクロシステムズ）が無制限の独占的な権利を保持しているが、オラクル（サン）はJava Community Process (JCP) と呼ばれるプロセスに参加し、当事者たちがJavaに関連する技術（言語、SDKからAPIに至るまで）に対する変更を専門家団体や諮問会議を通して提案することを許可している。JCP内の規定では、Javaに対する新しい仕様や変更はオラクル（サン）による承認が必要であるとされている。JCPは営利寄与者に対しては会費が必要としているが、非営利寄与者や個人は無料で参加できる。JavaのAPIセットにはいくつかのエディションがあり、標準エディションのJava SE、エンタープライズ向けエディションのJava EE、モバイル／組み込み環境向けエディションのJava MEが存在する。