C Sharp
C#(シーシャープ)は、マイクロソフトが開発した、汎用のオブジェクト指向プログラミング言語のひとつである。C#は、Javaに似た構文を持ち、C++に比べて扱いやすく、プログラムの記述量も少なくて済む。また、C#は、.NET Framework上で動作することを前提として開発された言語であり、WindowsアプリケーションやWebアプリケーションの開発に適している。
マルチパラダイムをサポートする汎用高レベルプログラミング言語で、静的型付け、タイプセーフ、スコープ、命令型、宣言型、関数型、汎用型、オブジェクト指向(クラスベース)、のプログラミング分野を含んでいる。
共通言語基盤 (CLI) といった周辺技術も含め、Microsoft社のフレームワークである「.NET Framework」の一部である。また、以前のVisual J++で「非互換なJava」をJavaに持ち込もうとしたMicrosoftとは異なり、その多くの全てではなく一部にプロプライエタリなコンポーネントもある。そのため、それに依存するものなど、後述のMonoなどで制限がある場合がある。仕様を積極的に公開し、標準化機構に託して自由な利用を許す (ECMA-334,ISO/IEC 23270:2003,JIS X 3015) など、同社の姿勢の変化があらわれている。
デンマーク人であるアンダース・ヘルスバーグが設計したプログラミング言語である。構文はC系言語(C,C++など)の影響を受けており、その他の要素には以前ヘルスバーグが所属していたボーランド設計のDelphiの影響が見受けられる。
開発にはボーランドのTurbo PascalやDelphiを開発したアンダース・ヘルスバーグを筆頭として多数のDelphi開発陣が参加している。
C#は共通言語基盤(共通言語ランタイムなど)が解釈する共通中間言語にコンパイルされて実行される。
自動ボックス化、デリゲート、
プロパティ、インデクサ、カスタム属性、ポインタ演算操作、構造体(値型オブジェクト)、多次元配列、可変長引数、などの機能を持つ。また、Javaと同様に大規模ライブラリ、プロセッサ・アーキテクチャに依存しない実行形態、ガベージコレクション、JITコンパイルによる実行の高速化、などが実現されている(もっともこれらはC#の機能というより.NET Frameworkによるものである)。
.NET構想における中心的な開発言語であり、XML WebサービスやASP.NETの記述にも使用される。他の.NET系の言語でも記述可能だが、。マイクロソフトの統合開発環境では、Microsoft Visual C#がC#に対応している。
共通言語仕様のCLSによって、他のCLS準拠の言語(Visual Basic .NETやVisual C++ (C++/CLI) など)と相互に連携することができる。
さまざまな意味において、基盤であるCLIの機能をもっとも反映している言語であるといえる。C#にある組み込み型のほとんどは、CLIフレームワークに実装されている値型と対応している。しかし、C#の言語仕様はコンパイラのコード生成については何も言及していないため、CLRに対応しなければならないとか、共通中間言語 (CIL) などの特定のフォーマットのコードを生成しなければならないとかいうことは述べられていない。そのため、理論的にはC++やFORTRANのように環境依存のマシン語を生成することも可能である。しかし、現在存在するすべてのC#コンパイラはCLIをターゲットにしている。
C#では、CやC++と比較してさまざまな制限や改良が加えられている。また、仕様の多くはC#言語というよりは、基盤である.NET Frameworkそのものに依拠している。Javaで導入された制限および改良をC#でも同様に採用しているものが多いが、C#で新たに導入された改良がのちにJavaにも同様に採用されたものもある。その例を次に挙げる。
外のブロックで宣言した変数と同じ名前の変数を、内のブロックで再宣言(シャドウ)してはいけない。再宣言は便利なこともあれば、混乱や曖昧のもとと主張されることもあるが、C#では禁止されている。
C#にはブール型boolが存在し、while文やif文のように条件をとるステートメントには、bool型の式を与えなければならない。C言語では、ブール型が無くint型(0を偽とし、非0を真とする)に兼用させた上、(ヌルポインタを偽とみなすこととするといろいろと便利だった、ということもあり)ポインタでもwhile文やif文に与える式にできる、という仕様としていた。これは便利なこともあったが、本来比較式を記述すべきところで誤って代入式を記述してもコンパイル適合となってしまうなど、ミスが見逃されることもあった。C#ではに、そのような仕様ではなくブール型を独立させ、またブール型を厳密に要求する場所を多くしている。
switch文に整数型あるいは整数型に準ずる型のみならず、文字列型stringを使用できる。caseラベルには、整数型あるいは整数型に準ずる型の定数のみならず、文字列リテラル(文字列定数)を使用できる。
組み込み型のサイズおよび内部表現が仕様で定められており、プラットフォームや処理系に依存しない。浮動小数点数はIEEE 754に準拠する。文字および文字列はUTF-16エンコーディングを採用する。
ポインタをサポートする。ポインタはunsafeスコープ内のみで使用することができ、適切な権限をもつプログラムのみがunsafeとマークされたコードを実行することができる。オブジェクトへのアクセスの大部分は管理された安全な参照によってなされ、大部分の算術演算はオーバフローのチェックがなされる。unsafeポインタは値型や文字列を指すことができる。セーフコードでは、必ずしもそうする必要はないものの、IntPtr型を通してポインタをやりとりすることができる。
マネージドなメモリを明示的に解放する方法は存在せず、参照されなくなったメモリはガベージコレクタによって自動的に解放される。ガベージコレクタは、メモリの解放忘れによって起こるメモリリークを解消する。C#は、データベース接続のようなアンマネージドなリソースに対しても明示的に制御する方法を提供している。これはIDisposableインタフェースとusingステートメントによってなされる。
グローバル変数やグローバルメソッドは存在しない。すべてのフィールドとメソッドはクラスあるいは構造体の一部として宣言されなければならない。
例えばC/C++のprintf()関数のように名前空間レベルに存在するフリー関数を定義することはできない。ほとんどの場合クラスおよび構造体は名前の衝突を避けるために名前空間に所属する。
名前空間は階層構造をもつ。つまり、名前空間は他の名前空間の中に宣言することができる。
組み込みの値型を含めたすべての型は、objectクラス (System.Object) の派生型である。つまりobjectクラスのもつすべてのプロパティやメソッドを継承する。例えば、すべての型はToString()メソッドをもつ。
クラス (class) は参照型であり、構造体 (struct) および列挙型 (enum) は値型である。構造体はクラスよりも軽量で、C/C++との相互運用性に優れるが、派生型を定義することができない。
クラスおよび構造体は複数のインタフェースを実装することができるが、多重継承はサポートされない。
C#はC++に比べて型安全である。既定の暗黙変換は、整数の範囲を広げる変換や、派生クラスから基底クラスへの変換といった、安全な変換のみに限定される。これは、コンパイル時、JITコンパイル時、そして一部の動的なケースでは実行時に強制される。ブール型と整数型、列挙型と整数型、の間は暗黙変換はできない。暗黙変換をユーザー定義する際は、明示的にそのように指定しなければならない。これはC++のコンストラクタとは違った仕様である。
列挙型のメンバーは、列挙型のスコープの中に置かれる。また、列挙型の定数名を取得することができる。さらに、列挙型の定数名から動的に定数値を得ることができる。
アクセサの定義と利用を簡略化するためにプロパティ構文を利用できる。C++およびJavaにおけるカプセル化では、通例getter/setterアクセサとなるメンバー関数あるいはメソッドを定義して利用するが、C#ではプロパティ機能により、カプセル化を維持しつつ、あたかもフィールドを直接読み書きするような直感的な構文でオブジェクトの状態にアクセスすることができる。プロパティによってメンバーのアクセス制御やデータの正当性チェックを実行することができる。なお、イベントハンドラーに利用するデリゲートのカプセル化にはイベント構文 (event) が用意されている。
部分型 (Partial Type) が導入された。以下のようにクラスや構造体の宣言にpartial修飾子をつけることで、その宣言を分割することができる。
これは以下と同義である:
これによって、巨大なクラスを分割したり、自動生成されたコードを分離したりすることができる。partial 修飾子はすべての宣言につける必要がある。
ジェネリクスが導入された。これは.NET Framework 2.0の機能である。クラス、構造体、インタフェース、デリゲート、メソッドに対して適用することができる。.NETのGenericsはC++のテンプレート、あるいはJavaにおけるそれとも異なるもので、コンパイルによってではなく実行時にランタイムによって特殊化される。これによって異なる言語間の運用を可能にし、リフレクションによって型パラメーターに関する情報を取得することができる。また、where節によって型パラメーターに制約を与えることができる。一方、C++のように型パラメーターとして式を指定することはできない。なお、ジェネリックメソッドの呼び出し時に引数によって型パラメーターが推論できる場合、型パラメーターの指定は省略できる。
静的クラスが導入された。static属性をクラスの宣言につけることで、クラスはインスタンス化できなくなり、静的なメンバーしか持つことができなくなる。
イテレータ#C# 2.0を参照。
yieldキーワードによるコルーチンを使うことで、イテレータの生成を楽に実装できるようになった。
クロージャの機能を提供する匿名デリゲートが導入された。
プロパティのget もしくは setアクセサのどちらかにアクセス修飾子を指定することでアクセス制御が別個にできるようになった。次の例では、getアクセサはpublic、setアクセサはprivateである。
nullを保持できる値型、Nullableが導入された。
int?はNullable<int>の糖衣構文である。また、nullを保持しているNull許容型のインスタンスをボックス化しようとすると、単に空参照 (null) に変換される。
また、null結合演算子 (??)が導入された。これは、nullでない最初の値を返す。
この演算子は主にNullable型を非Nullable型に代入するときに使われる。
External Aliases/Namespace Aliases Qualifiers
プラグマディレクティブ/C言語の様な固定長の配列表記Fix Size Buffers
var キーワードが導入され、型推論を利用したローカル変数の宣言ができるようになった。
拡張メソッド (extension method) が導入された。既存のクラスを継承して新たなクラスを定義することなく、新たなインスタンスメソッドを疑似的に追加定義することができる。具体的には、入れ子になっていない、非ジェネリックの静的クラス内に、this 修飾子をつけた、拡張メソッドを追加する対象の型の引数を最初に持つメソッドをまず定義する。これによって、通常の静的メソッドとしての呼び出しの他に、指定した型のインスタンスメソッドとしての呼び出しを行うことができるメソッドを作ることができる。以下に例を挙げる:
この例は、文字列(string 型のインスタンス)を指定した回数繰り返し連結したものを返すメソッド Repeat を、既存の string 型に追加している。このメソッドは、以下のように呼び出すことができる:
また、列挙型やインタフェースなど本来メソッドの実装を持ち得ない型に、見かけ上インスタンスメソッドを追加することも可能である。以下に例を挙げる:
このメソッドは以下のように呼び出すことができる:
拡張メソッドは糖衣構文の一種であり、カプセル化の原則に違反するものではないが、必要な場合に限り注意して実装することがガイドラインとして推奨されている。
部分メソッドが導入された。部分型(partial 型)内で定義された private で、かつ戻り値が void のメソッドに partial 修飾子をつけることでメソッドの宣言と定義を分離させることができる。定義されていない部分メソッドは何も行わず、何らエラーを発生させることもない。例えば:
上のコードにおいて Method() を呼び出すと、<tt>Did something.</tt> と表示されるだけだが、ここで以下のコード:
を追加した上で Method() を呼び出すと、<tt>[DEBUG: Some message] Did something.</tt> と表示される。
ラムダ式が導入された。この名前はラムダ計算に由来する。
は、ラムダ式を使って次のように記述できる:
ラムダ式は匿名メソッドと同様に扱えるが、式形式のラムダがExpression<TDelegate>型として扱われた場合のみ匿名メソッドとして扱われず、コンパイラによって式木を構築するコードに変換される。匿名デリゲートが実行前にコンパイルされたCILを保持するのに対し、式木はCILに実行時コンパイル可能であるDOMのような式の木構造そのものを保持する。これはLINQクエリをSQLクエリなどに変換する際に役立つ。
以下は、3つの任意の名前の変数、整数、括弧、及び四則演算子のみで構成された式を逆ポーランド記法に変換する汎用的なコードである:
オブジェクトの初期化が式として簡潔に記述できるようになった。
また、コレクションの初期化も同様に簡潔に記述できるようになった。
但し、上のコードでは匿名の変数に便宜的に __p、__l、__d と命名している。実際はプログラマはこの変数にアクセスすることはできない。
プロパティをより簡潔に記述するための自動実装プロパティが導入された。プロパティの定義に get; set; と記述することで、プロパティの値を保持するための匿名のフィールド(プログラマは直接参照することはできない)と、そのフィールドにアクセスするためのアクセサが暗黙に定義される。また、C# 5.0 までは get;とset;のどちらか片方だけを記述することは出来なかったが、C# 6.0 からは get; のみが可能。以下のコード:
は、以下のようなコードに相当する動作をする:
但し、上のコードでは匿名のフィールドに便宜的に __value と命名している。実際はプログラマはこのフィールドにアクセスすることはできない。
一時的に使用される型を簡単に定義するための匿名型が導入された。以下に例を挙げる:
上の式は、以下の内容のクラスを暗黙に定義する。定義されたクラスは匿名であるが故にプログラマは参照できない。
同じ型、同じ名前のプロパティを同じ順序で並べた匿名型は同じであることが保証されている。即ち、以下のコード:
において、her.GetType() == him.GetType() は true である。
new キーワードを用いた配列の宣言の際、型を省略できるようになった。匿名型の配列を宣言する際に威力を発揮する。
LINQ をサポートするために、クエリ式が導入された。これは SQL の構文に類似しており、最終的に通常のメソッド呼び出しに変換されるものである。以下に例を示す:
上のコードは以下のように変換される:
C# 3.0で追加された構文の多くは式であるため、より巨大な式(当然クエリ式も含まれる)の一部として組み込むことができる。旧来複数の文に分けたり、作業用の変数を用意して記述していたコードを単独の式としてより簡潔に記述できる可能性がある。
出井秀行著の『実戦で役立つ C#プログラミングのイディオム/定石&パターン』(技術評論社、2017年)という書籍ではクエリ構文よりメソッド構文を推奨しており、クエリ構文ではLINQの全ての機能を使用できるわけではないこと、メソッド呼び出しは処理を連続して読める可読性があること、メソッド呼び出しであればMicrosoft Visual Studioの強力なインテリセンスが利用できることを理由に、著者はクエリ構文をほとんど使用していないと記している。
dynamicキーワードが導入され、動的型付け変数を定義できるようになった。dynamic型として宣言されたオブジェクトに対する操作のバインドは実行時まで遅延される。
VBやC++に実装されているオプション引数・名前付き引数が、C#でも利用できるようになった。
ジェネリクスの型引数に対してin、out修飾子を指定することにより、ジェネリクスの共変性・反変性を指定できるようになった。
非同期処理 (async/await)
foreach の仕様変更
get のみの自動実装プロパティおよびコンストラクタ代入
静的 using ディレクティブ
catch/finally での await
式形式のメンバー (expression-bodied members)
文字列補間(テンプレート文字列)
nameof 演算子
#pragma
静的 using ディレクティブを利用することで、型名の指定無しに他クラスの静的メンバーの呼び出しを行えるようになった。利用するにはusing staticの後に完全修飾なクラス名を指定する。
catchの後にwhenキーワードを使用することで、処理する例外を限定することができるようになった。
パターンマッチング (is 式/switch 文)
タプル (タプル記法/分解/値の破棄)
数値リテラルの改善(桁セパレータ/バイナリリテラル)
ref戻り値、ref変数
Expression-bodied 機能の拡充(コンストラクタ/デストラクタ/get/set/add/remove)
Throw 式
out引数で値を受け取る場合、その場所で変数宣言可能となった。
is式の構文が拡張され、型の後ろに変数名を宣言できるようになった。
拡張されたis式はマッチした場合に宣言した変数にキャストした値を代入し、さらにtrueと評価される。
マッチしなかった場合はfalseと評価され、宣言した変数は未初期化状態となる。
switch文のマッチ方法が拡張され、caseラベルに従来の「定数パターン」に加え、新たに「型パターン」を指定できるようになった。
また、「型パターン」のcaseラベルでは、when句に条件を指定することができる。
「型パターン」を含むswitch文では、必ずしも条件が排他的でなくなったため、最初にマッチしたcaseラベルの処理が実行される。
タプルのための軽量な構文が導入された。従来のSystem.Tupleクラスとは別に、System.ValueTuple構造体が新しく追加された。
2個以上の要素を持つタプルのための記法が導入された。
引数リストと同様の形式で、タプルを記述できる。
多値戻り値を簡単に扱えるように、分解がサポートされた。
分解はタプルに限らない。Deconstruct()メソッドが定義されたクラスでも、分解を利用できる。
以下に、DateTime型に分解を導入する例を示す。
上記のコードでDateTime型にDeconstruct()拡張メソッドを定義し、
のように左辺で3つの変数に値を受け取ることができる。
分解、out引数、パターンマッチングで、値の破棄を明示するために_が利用できるようになった。
破棄された値は、後で参照することはできない。
refキーワードの使用方法が拡張された。これによって、安全な参照の使い道が広がった。
戻り値の型をrefで修飾することで、オブジェクトの参照を戻り値とすることができる。
変数の寿命は変わらないため、メソッド終了時に破棄されるローカル変数をref戻り値とすることはできない。
ローカル変数の型をrefで修飾することで、参照を代入することができる。
Mainメソッドの戻り値として、Task型、Task(int)型が認められた。
型推論可能な場面では、defaultの型指定は省略可能となった。
C#7.2で追加された仕様は以下の通り。
値型におけるパフォーマンス向上を意図した複数の機能が追加された。
引数にinを指定することで、読み取り専用参照渡しを指定できる。
また、戻り値にref readonlyを指定することで、読み取り専用参照戻り値を指定できる。
これにより、構造体のコピーを避けると共に、意図しない値の変更を抑止できる。
構造体宣言時にreadonlyを指定することで、真の読み取り専用構造体を定義できる。
readonly構造体の全てのフィールドはreadonlyでなければならず、thisポインタも読み取り専用となる。
これにより、メンバーアクセス時の意図しない防御的コピーを抑止できる。
構造体宣言時にrefを指定することで、ヒープ領域へのコピーを防ぐ構造体がサポートされる。
ref構造体では、box化できない、配列を作成できない、型引数になることができない、など、ヒープ領域へのコピーを防ぐための厳しい制限がかかる。
この機能は、Span<T>のような構造体をサポートするために利用され、unsafe文脈以外でのstackallocの利用をも可能とする。
C#4.0で追加された名前付き引数が末尾以外でも利用できるようになった。
同一アセンブリ内、かつ、継承先からのアクセス許可を表すprivate protectedアクセス修飾子が追加された。
十六進リテラルの0x、二進リテラルの0bの直後のアンダースコアが認められた。
C#7.3では以下の仕様が追加された。
System.Enum, System.Delegate
unmanaged (文脈キーワード)
refローカル変数の再割り当て
stackalloc初期化子
Indexing movable fixed buffers
カスタムfixedステートメント
C# 8.0で追加された仕様は以下の通り。
参照型にnull許容性を指定できるようになった。参照型の型名に?を付加した場合にnull許容参照型となる。
参照型の型名に?を付加しない場合、null非許容参照型となる。
フロー解析レベルでのnull許容性チェックが行われる。null許容値型のNullable<T>のような新しい型は導入されない。
参照型のnull許容性は、null許容コンテキストによって有効、無効の切り替えが可能である。
C#7.3以前の互換性のために、既定では無効となっている。
Nullable コンパイルオプション: プロジェクト全体でのnull許容コンテキストを指定する
#nullable ディレクティブ: ソースコードの部分ごとにnull許容コンテキストを指定する
annotations オプション、warningsオプションにより、適用範囲を限定できる
null許容参照型の変数名の後に !を使用することで、フロー解析時の警告が免除される。
インタフェースのメンバーに既定の実装を指定できるようになった。また、インタフェースに静的メンバーを持つことができるようになった。
さらに、インタフェースのメンバーにアクセシビリティを指定できるようになった。
既定のアクセシビリティは、従来通り public となる。
実装があるインスタンスメンバーは、既定で virtual となり override可能である。
実装をoverrideさせないためにsealedを指定することができる。
switch式が追加された。
プロパティパターン、タプルパターン、位置指定パターンの追加により、再帰的なパターンマッチングが可能になった。
IAsyncEnumerable<T>
インタフェースを返すことで、イテレータ構文と非同期構文の共存が可能になった。
await foreachによって非同期ストリームを列挙する。
IndexとRangeを指定できる専用構文が追加された。
using 宣言
式中のstackalloc
C# 9.0で追加された仕様は以下の通り。
ネイティブサイズの整数型(nint nuint型)
関数ポインタ(delegate*型)
C# 10.0で追加された仕様は以下の通り。
global using ディレクティブ
ファイル スコープの名前空間の宣言
const 補間文字列を許可する
レコードの型で ToString() を封印できる
メソッドで AsyncMethodBuilder 属性を許可する
CallerArgumentExpression 属性
拡張 #line pragma
警告ウェーブ 6
C#の言語仕様は標準化団体Ecma Internationalを通じて公開・標準化されており、第三者がマイクロソフトとは無関係にコンパイラや実行環境を実装することができる。
現段階で、C#コンパイラの実装は次の5つが知られている。
Visual Studio 2015 以降で使用されている、.NETコンパイラプラットフォーム (コードネームRoslyn)。ApacheライセンスのオープンソースプロジェクトでGitHubで公開されている。Windows、macOS、Linuxで動作する。C#のコンパイラはC#、VB.NETのコンパイラはVB.NETで実装されている。以前のコンパイラと比べて、リファクタリングやIDE、スクリプティングなどへの利用が可能なAPIが公開されており、コンパイラ以外への様々な応用が可能。
Visual Studio 2013 まで使われていた、マイクロソフトによるVisual C# コンパイラ。
2006年のC# 2.0当時の、マイクロソフトによる。共通言語基盤 (CLI) とC#コンパイラがソースコードで公開されている。
によるMono内の Mono Compiler Suite (mcs)。
2012年まで開発されていた、によるPortable.NET内の the C-Sharp code compiler (cscc)。
ECMA-334 3rd/4th/5th edition によると、C# は「C Sharp」(シーシャープ)と発音し、LATIN CAPITAL LETTER C (U+0043) の後に NUMBER SIGN # (U+0023) と書く。 音楽のシャープ (♯, MUSIC SHARP SIGN (U+266F)) ではなくナンバーサイン (#) を採用したのは、フォントやブラウザなどの技術的な制約に加え、ASCIIコードおよび標準的キーボードには前者の記号が存在しないためである。
"#"接尾辞は、他の.NET言語にも使用されており、J#(Javaのマイクロソフトによる実装)、A#(Adaから)、F#(System Fなどから)が含まれる。また"#"接尾辞はGtk#(GTKなどのGNOMEライブラリの.NETラッパ)、Cocoa#(Cocoa (API)のラッパ)などのライブラリにも使用されている。そのほか、SharpDevelopなどの"Sharp"を冠する関連ソフトウェアも存在する。
C#という名称の解釈として、「(A-Gで表された)直前の音を半音上げる」という音楽記号の役割に着目し、「C言語を改良したもの」を意味したのではないか、。これは、C++の名称が「C言語を1つ進めたもの」という意味でつけられたことにも似ている。
アンダース・ヘルスバーグは、「C#」が「C++++」(すなわち「C++をさらに進めたもの」)にみえるのが由来である、と語っている。