C Sharp
C#のロゴ | |
パラダイム | 構造化プログラミング、命令型プログラミング、オブジェクト指向プログラミング、イベント駆動型プログラミング、関数型プログラミング、ジェネリックプログラミング、リフレクション、クラスベース、正格プログラミング、マルチパラダイムプログラミング |
---|---|
登場時期 | 2000年 |
設計者 | マイクロソフト(アンダース・ヘルスバーグ率いるチーム) |
開発者 | マイクロソフト |
最新リリース | 8/ 2019年9月23日 |
型付け | 強い静的型付け(4.0から動的型導入) |
主な処理系 | CLR, Mono |
影響を受けた言語 | C++、C言語、Java、Delphi、Modula-3、Cω、Eiffel、F Sharp、Haskell、Icon、J Sharp、Microsoft Visual J++、Object Pascal、Rust、ML、Visual Basic |
影響を与えた言語 | D言語, F#, Java, Nemerle, Vala |
プラットフォーム | Windows, macOS, Linuxなど |
ライセンス | Apacheライセンス (Roslyn) |
ウェブサイト |
docs |
拡張子 | cs、csx |
C#(シーシャープ)は、アンダース・ヘルスバーグが設計したプログラミング言語であり、構文はその名前にもある通りC系言語(C言語、C++やJavaなど)の影響があるが、構文以外の言語機能などについてはヘルスバーグが以前の所属であるボーランドで設計したDelphiからの影響がある[注釈 1]。
マイクロソフトによる謳い文句としては、マルチパラダイムプログラミング言語、強い型付け、命令型、宣言型、手続き型、関数型、ジェネリック、オブジェクト指向の要素を持つ、などといった点が強調されている。
共通言語基盤 (CLI) といった周辺技術も含め、マイクロソフトのフレームワーク「.NET Framework」の一部である他、Visual J++で「非互換なJava」をJavaに持ち込もうとしたような以前のマイクロソフトとは異なり、その多くの[注釈 2]仕様を積極的に公開し標準化機構に託して自由な利用を許す (ECMA-334, ISO/IEC 23270:2003, JIS X 3015) など、同社の姿勢の変化があらわれている一面でもある(実際に「Mono」という、フリーソフトウェアの定義に合致したライセンスの、コミュニティによる実装がある)。
概要
開発にはボーランドのTurbo PascalやDelphiを開発したアンダース・ヘルスバーグを筆頭に多数のDelphi開発陣が参加している。
C#は共通言語基盤(共通言語ランタイムなど)が解釈する共通中間言語にコンパイルされて実行される。
自動ボックス化、デリゲート、 プロパティ、インデクサ、カスタム属性、ポインタ演算操作、構造体(値型オブジェクト)、多次元配列、可変長引数、などの機能を持つ。また、Javaと同様に大規模ライブラリ、プロセッサ・アーキテクチャに依存しない実行形態、ガベージコレクション、JITコンパイルによる実行の高速化、などが実現されている(もっともこれらはC#の機能というより.NET Frameworkによるものである)。
.NET構想における中心的な開発言語であり、XML WebサービスやASP.NETの記述にも使用される。他の.NET系の言語でも記述可能だが、生産性・機能においてC#が最も優れるとされる[要出典]。マイクロソフトの統合開発環境では、Microsoft Visual C#がC#に対応している。
共通言語仕様のCLSによって、他のCLS準拠の言語(Visual Basic .NETやVisual C++ (C++/CLI) など)と相互に連携することができる。
バージョンおよびリリース時期
バージョン | 言語仕様 | リリース時期 | 実装 | Visual Studio | ||
---|---|---|---|---|---|---|
ECMA[2][3] | ISO/IEC | マイクロソフト | ||||
1.0 |
ECMA-334:2003 (2002年12月) |
ISO/IEC 23270:2003 (2003年4月) |
2002年1月 | 2002年1月 | .NET Framework 1.0 | .NET (2002) |
1.1 1.2 |
2003年10月 | 2003年4月 | .NET Framework 1.1 | .NET 2003 | ||
2.0 |
ECMA-334:2006 (2006年6月) |
ISO/IEC 23270:2006 (2006年9月) |
2005年9月 | 2005年11月 | .NET Framework 2.0 | 2005 |
3.0 | — | — | 2007年8月 | 2007年11月 | 2008 2010 | |
4.0 | — | — | 2010年4月 | 2010年4月 | .NET Framework 4 | 2010 |
5.0 |
ECMA-334:2017 (2017年12月) |
ISO/IEC 23270:2018 (2018年12月) |
2013年6月 | 2012年8月 | .NET Framework 4.5 | 2012 2013 |
6.0 | 未定 | 未定 | Draft | 2015年7月 |
|
2015 |
7.0 | 未定 | 未定 | — | 2017年3月 | .NET Framework 4.6.2 | 2017 |
7.1 | — | — | — | 2017年8月 |
|
2017 version 15.3[5] |
7.2 | — | — | — | 2017年11月 |
|
2017 version 15.5[7] |
7.3 | — | — | — | 2018年5月 |
|
2017 version 15.7[8] |
8.0 | — | — | — | 2019年9月 | .NET Core 3.0[9] |
2019 version 16.3[10] |
言語仕様
さまざまな意味において、基盤であるCLIの機能をもっとも反映している言語であるといえる。C#にある組み込み型のほとんどは、CLIフレームワークに実装されている値型と対応している。しかし、C#の言語仕様はコンパイラのコード生成については何も言及していない。つまり、CLRに対応しなければならないとか、共通中間言語 (CIL) などの特定のフォーマットのコードを生成しなければならないとかいうことは述べられていない。そのため、理論的にはC++やFORTRANのように環境依存のマシン語を生成することも可能である。しかし、現在存在するすべてのC#コンパイラはCLIをターゲットにしている。
CやC++からの改良点
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に準拠する[11]。文字および文字列はUTF-16エンコーディングを採用する[12]。
ポインタとメモリ管理
- ポインタをサポートする。ポインタは
unsafe
スコープ内のみで使用することができ、適切な権限をもつプログラムのみがunsafe
とマークされたコードを実行することができる。オブジェクトへのアクセスの大部分は管理された安全な参照によってなされ、大部分の算術演算はオーバフローのチェックがなされる。unsafe
ポインタは値型や文字列を指すことができる。セーフコードでは、必ずしもそうする必要はないものの、IntPtr
型を通してポインタをやりとりすることができる。 - マネージドなメモリを明示的に解放する方法は存在せず、参照されなくなったメモリはガベージコレクタによって自動的に解放される。ガベージコレクタは、メモリの解放忘れによって起こるメモリリークを解消する。C#は、データベース接続のようなアンマネージドなリソースに対しても明示的に制御する方法を提供している。これは
IDisposable
インターフェイスとusing
ステートメントによってなされる。
名前空間とオブジェクト指向な型システム
- 名前空間は階層構造をもつ。つまり、名前空間は他の名前空間の中に宣言することができる。
- 組み込みの値型を含めたすべての型は、
object
クラス (System.Object
) の派生型である。つまりobject
クラスのもつすべてのプロパティやメソッドを継承する。例えば、すべての型はToString()
メソッドをもつ。 - クラス (
class
) は参照型であり、構造体 (struct
) および列挙型 (enum
) は値型である。構造体はクラスよりも軽量で、C/C++との相互運用性に優れるが、派生型を定義することができない。 - クラスおよび構造体は複数のインターフェイスを実装することができるが、多重継承はサポートされない。
- C#はC++に比べて型安全である。既定の暗黙変換は、整数の範囲を広げる変換や、派生クラスから基底クラスへの変換といった、安全な変換のみに限定される。これは、コンパイル時、JITコンパイル時、そして一部の動的なケースでは実行時に強制される。ブール型と整数型、列挙型と整数型、の間は暗黙変換はできない。暗黙変換をユーザー定義する際は、明示的にそのように指定しなければならない。これはC++のコンストラクタとは違った仕様である。
- 列挙型のメンバーは、列挙型のスコープの中に置かれる。また、列挙型の定数名を取得することができる。さらに、列挙型の定数名から動的に定数値を得ることができる。
- アクセサの定義と利用を簡略化するためにプロパティ構文を利用できる。C++およびJavaにおけるカプセル化では、通例getter/setterアクセサとなるメンバー関数あるいはメソッドを定義して利用するが、C#ではプロパティ機能により、カプセル化を維持しつつ、あたかもフィールドを直接読み書きするような直感的な構文でオブジェクトの状態にアクセスすることができる。プロパティによってメンバーのアクセス制御やデータの正当性チェックを実行することができる。なお、イベントハンドラーに利用するデリゲートのカプセル化にはイベント構文 (
event
) が用意されている。
C# 2.0からの仕様
部分型
部分型 (Partial Type) が導入された。以下のようにクラスや構造体の宣言にpartial
修飾子をつけることで、その宣言を分割することができる。
partial class MyClass { int a; }
partial class MyClass { int b; }
これは以下と同義である:
class MyClass { int a; int b; }
これによって、巨大なクラスを分割したり、自動生成されたコードを分離したりすることができる。partial
修飾子はすべての宣言につける必要がある。
ジェネリクス
ジェネリクスが導入された。これは.NET Framework 2.0の機能である。クラス、構造体、インターフェイス、デリゲート、メソッドに対して適用することができる。.NETのGenericsはC++のテンプレート、あるいはJavaにおけるそれとも異なるもので、コンパイルによってではなく実行時にランタイムによって特殊化される。これによって異なる言語間の運用を可能にし、リフレクションによって型パラメーターに関する情報を取得することができる。また、where
節によって型パラメーターに制約を与えることができる。一方、C++のように型パラメーターとして式を指定することはできない。なお、ジェネリックメソッドの呼び出し時に引数によって型パラメーターが推論できる場合、型パラメーターの指定は省略できる。
静的クラス
静的クラスが導入された。static
属性をクラスの宣言につけることで、クラスはインスタンス化できなくなり、静的なメンバーしか持つことができなくなる。
yieldキーワード
yield
キーワードによるコルーチンを使うことで、イテレータの生成を楽に実装できるようになった。
匿名デリゲート
プロパティに対する個別のアクセス制御
プロパティのget
もしくは set
アクセサのどちらかにアクセス修飾子を指定することでアクセス制御が別個にできるようになった。次の例では、get
アクセサはpublic
、set
アクセサはprivate
である。
public class MyClass
{
private string status = string.Empty;
public string Status
{
get { return status; }
private set { status = value; }
}
}
Null許容型とnull結合演算子
nullを保持できる値型、Nullable
が導入された。
int? i = 512;
i = null;
int? j = i + 500; //jはnullとなる。nullとの演算の結果はnullになる。
int?
はNullable<int>
の糖衣構文である。また、nullを保持しているNull許容型のインスタンスをボックス化しようとすると、単に空参照 (null) に変換される[13]。
int? x = null;
object o = x;
System.Console.WriteLine(o == null); //Trueが出力される
また、null結合演算子 (??
)が導入された。これは、null
でない最初の値を返す。
object obj1 = null;
object obj2 = new object();
object obj3 = new object();
return obj1 ?? obj2 ?? obj3; // obj2 を返す
この演算子は主にNullable
型を非Nullable
型に代入するときに使われる。
int? i = null;
int j = i ?? -1; // nullをint型に代入することはできない
C# 3.0からの仕様
varキーワード
var
キーワードが導入され、型推論を利用したローカル変数の宣言ができるようになった。
var s = "foo";
// 上の文は右辺が string 型であるため、次のように解釈される:
string s = "foo";
// 以下に挙げる文は誤りである(コンパイルエラーとなる):
var v; // 初期化式を欠いている (型を推論する対象が存在しない)
var v = null; // 型が推論できない (曖昧である)
拡張メソッド
拡張メソッド (extension method) が導入された。既存のクラスを継承して新たなクラスを定義することなく、新たなインスタンスメソッドを疑似的に追加定義することができる。具体的には、入れ子になっていない、非ジェネリックの静的クラス内に、this
修飾子をつけた、拡張メソッドを追加する対象の型の引数を最初に持つメソッドをまず定義する。これによって、通常の静的メソッドとしての呼び出しの他に、指定した型のインスタンスメソッドとしての呼び出しを行うことができるメソッドを作ることができる。以下に例を挙げる:
public static class StringUtil
{
public static string Repeat(this string str, int count)
{
var array = new string[count];
for (var i = 0; i < count; ++i) array[i] = str;
return string.Concat(array);
}
}
この例は、文字列(string
型のインスタンス)を指定した回数繰り返し連結したものを返すメソッド Repeat
を、既存の string
型に追加している。このメソッドは、以下のように呼び出すことができる:
// 静的メソッドとしての呼び出し
StringUtil.Repeat("foo", 4);
// 拡張メソッドとしての呼び出し
"foo".Repeat(4);
// (どちらの例も "foofoofoofoo" を返す)
また、列挙型やインターフェイスなど本来メソッドの実装を持ち得ない型に、見かけ上インスタンスメソッドを追加することも可能である。以下に例を挙げる:
public enum Way
{
None, Left, Right, Up, Down
}
public static class EnumUtil
{
public static Way Reverse(this Way src)
{
switch (src)
{
case Way.Left: return Way.Right;
case Way.Right: return Way.Left;
case Way.Up: return Way.Down;
case Way.Down: return Way.Up;
default: return Way.None;
}
}
}
このメソッドは以下のように呼び出すことができる:
Way l = Way.Left;
Way r = l.Reverse(); // Way.Right
拡張メソッドは糖衣構文の一種であり、カプセル化の原則に違反するものではないが、必要な場合に限り注意して実装することがガイドラインとして推奨されている[14]。
部分メソッド
部分メソッドが導入された。部分型(partial
型)内で定義された private
で、かつ戻り値が void
のメソッドに partial
修飾子をつけることでメソッドの宣言と定義を分離させることができる。定義されていない部分メソッドは何も行わず、何らエラーを発生させることもない。例えば:
partial class Class
{
partial void DebugOutput(string message);
void Method()
{
DebugOutput("Some message");
Console.WriteLine("Did something.");
}
}
上のコードにおいて Method()
を呼び出すと、Did something. と表示されるだけだが、ここで以下のコード:
partial class Class
{
partial void DebugOutput(string message)
{
Console.Write("[DEBUG: {0}] ", message);
}
}
を追加した上で Method()
を呼び出すと、[DEBUG: Some message] Did something. と表示される。
ラムダ式
匿名メソッドをより簡略化した記法として、ラムダ式が導入された。この名前はラムダ計算に由来する。
以下の匿名メソッド
// iを変数としてi+1を返すメソッド
delegate (int i) { return i + 1; }
は、ラムダ式を使って次のように記述できる:
(int i) => i + 1; /* 式形式のラムダ */
//或いは:
(int i) => { return i + 1; }; /* ステートメント形式のラムダ */
ラムダ式は匿名メソッドと同様に扱えるが、式形式のラムダがExpression<TDelegate>
型として扱われた場合のみ匿名メソッドとして扱われず、コンパイラによって式木を構築するコードに変換される。匿名デリゲートが実行前にコンパイルされたCILを保持するのに対し、式木はCILに実行時コンパイル可能であるDOMのような式の木構造そのものを保持する。これはLINQクエリをSQLクエリなどに変換する際に役立つ。
以下は、3つの任意の名前の変数、整数、括弧、及び四則演算子のみで構成された式を逆ポーランド記法に変換する汎用的なコードである:
public static string ToRPN(Expression<Func<int, int, int, int>> expression)
{
return Parse((BinaryExpression) expression.Body).TrimEnd(' ');
}
private static string Parse(BinaryExpression expr)
{
string str = "";
if (expr.Left is BinaryExpression)
{
str += Parse((BinaryExpression) expr.Left);
}
else if (expr.Left is ParameterExpression)
{
str += ((ParameterExpression) expr.Left).Name + " ";
}
else if (expr.Left is ConstantExpression)
{
str += ((ConstantExpression) expr.Left).Value + " ";
}
if (expr.Right is BinaryExpression)
{
str += Parse((BinaryExpression) expr.Right);
}
else if (expr.Right is ParameterExpression)
{
str += ((ParameterExpression) expr.Right).Name + " ";
}
else if (expr.Right is ConstantExpression)
{
str += ((ConstantExpression) expr.Right).Value + " ";
}
return str + expr.NodeType.ToString()
.Replace("Add", "+")
.Replace("Subtract", "-")
.Replace("Multiply", "*")
.Replace("Divide", "/")
+ " ";
}
// 呼び出し例:
ToRPN((x, y, z) => (x + 1) * ((y - 2) / z)); // "x 1 + y 2 - z / *" を返す
オブジェクト初期化の簡略化
オブジェクトの初期化が式として簡潔に記述できるようになった。
var p = new Point { X = 640, Y = 480 };
// 上の文は次のように解釈される:
Point __p = new Point();
__p.X = 640;
__p.Y = 480;
Point p = __p;
また、コレクションの初期化も同様に簡潔に記述できるようになった。
var l = new List<int> {1, 2, 3};
var d = new Dictionary<string, int> {{"a", 1}, {"b", 2}, {"c", 3}};
// 上の文は次のように解釈される:
List<int> __l = new List<int>();
__l.Add(1);
__l.Add(2);
__l.Add(3);
List<int> l = __l;
Dictionary<string, int> __d = new Dictionary<string, int>();
__d.Add("a", 1);
__d.Add("b", 2);
__d.Add("c", 3);
Dictionary<string, int> d = __d;
但し、上のコードでは匿名の変数に便宜的に __p、__l、__d と命名している。実際はプログラマはこの変数にアクセスすることはできない。
自動実装プロパティ
プロパティをより簡潔に記述するための自動実装プロパティが導入された。プロパティの定義に get; set;
と記述することで、プロパティの値を保持するための匿名のフィールド(プログラマは直接参照することはできない)と、そのフィールドにアクセスするためのアクセサが暗黙に定義される。また、C# 5.0 までは get;
とset;
のどちらか片方だけを記述することは出来なかったが、C# 6.0 からは get;
のみが可能。以下のコード:
public int Value { get; set; }
は、以下のようなコードに相当する動作をする:
private int __value;
public int Value
{
get { return __value; }
set { __value = value; }
}
但し、上のコードでは匿名のフィールドに便宜的に __value
と命名している。実際はプログラマはこのフィールドにアクセスすることはできない。
匿名型
一時的に使用される型を簡単に定義するための匿名型が導入された。以下に例を挙げる:
new { Name = "John Doe", Age = 20 }
上の式は、以下の内容のクラスを暗黙に定義する。定義されたクラスは匿名であるが故にプログラマは参照できない。
public string Name { get; }
public int Age { get; }
同じ型、同じ名前のプロパティを同じ順序で並べた匿名型は同じであることが保証されている。即ち、以下のコード:
var her = new { Name = "Jane Doe", Age = 20 }
var him = new { Name = "John Doe", Age = 20 }
において、her.GetType() == him.GetType()
は true
である。
配列宣言の型省略
new
キーワードを用いた配列の宣言の際、型を省略できるようになった。匿名型の配列を宣言する際に威力を発揮する。
var a = new[] {"foo", "bar", null};
// 上の文は次のように解釈される:
string[] a = new string[] {"foo", "bar", null};
// 以下の文:
var a = new[] {"foo", "bar", 123};
// は次のように解釈されることなく、誤りとなる:
object[] a = new object[] {"foo", "bar", 123};
クエリ式
LINQ をサポートするために、クエリ式が導入された。これは SQL の構文に類似しており、最終的に通常のメソッド呼び出しに変換されるものである。以下に例を示す:
var passedStudents =
from s in students
where s.MathScore + s.MusicScore + s.EnglishScore > 200
select s.Name;
上のコードは以下のように変換される:
var passedStudents = students
.Where(s => s.MathScore + s.MusicScore + s.EnglishScore > 200)
.Select(s => s.Name);
C# 3.0で追加された構文の多くは式であるため、より巨大な式(当然クエリ式も含まれる)の一部として組み込むことができる。旧来複数の文に分けたり、作業用の変数を用意して記述していたコードを単独の式としてより簡潔に記述できる可能性がある。
出井秀行著の『実戦で役立つ C#プログラミングのイディオム/定石&パターン』(技術評論社、2017年)という書籍ではクエリ構文よりメソッド構文を推奨しており、クエリ構文ではLINQの全ての機能を使用できるわけではないこと、メソッド呼び出しは処理を連続して読める可読性があること、メソッド呼び出しであればMicrosoft Visual Studioの強力なインテリセンスが利用できることを理由に、著者はクエリ構文をほとんど使用していないと記している。
C# 4.0からの仕様
dynamicキーワード
dynamicキーワードが導入され、動的型付け変数を定義できるようになった。dynamic型として宣言されたオブジェクトに対する操作のバインドは実行時まで遅延される。
// xはint型と推論される:
var x = 1;
// yはdynamic型として扱われる:
dynamic y = 2;
public dynamic GetValue(dynamic obj)
{
// objにValueが定義されていなくとも、コンパイルエラーとはならない:
return obj.Value;
}
オプション引数・名前付き引数
VBやC++に実装されているオプション引数・名前付き引数が、C#でも利用できるようになった。
public void MethodA()
{
// 第1引数と第2引数を指定、第3引数は未指定:
Console.WriteLine("Ans: " + MethodB(1, 2)); // Ans: 3 … 1 + 2 + 0となっている
// 第1引数と第3引数を指定、第2引数は未指定:
Console.WriteLine("Ans: " + MethodB(A: 1, C: 3)); // Ans: 4 … 1 + 0 + 3となっている
}
// 引数が指定されなかった場合のデフォルト値を等号で結ぶ:
public int MethodB(int A = 0, int B = 0, int C = 0)
{
return A + B + C;
}
ジェネリクスの共変性・反変性
ジェネリクスの型引数に対してin、out修飾子を指定することにより、ジェネリクスの共変性・反変性を指定できるようになった。
IEnumerable<string> x = new List<string> { "a", "b", "c" };
// IEnumerable<T>インターフェイスは型引数にout修飾子が指定されているため、共変である。
// したがって、C# 4.0では次の行はコンパイルエラーにならない
IEnumerable<object> y = x;
C# 5.0からの仕様
- 非同期処理 (await, async)
- Caller Info 属性
- foreach の仕様変更
C# 6.0からの仕様
- 自動実装プロパティの初期化子
- get のみの自動実装プロパティおよびコンストラクタ代入
- 静的 using ディレクティブ
- Dictionary 初期化子
- catch/finally での await
- 例外フィルタ
- 式形式のメンバー (expression-bodied members)
- null条件演算子
- 文字列補間(テンプレート文字列)
- nameof 演算子
- #pragma
- コレクションの初期化子での拡張メソッド
- オーバーロード解決の改善
静的 using ディレクティブ
静的 using ディレクティブを利用することで、型名の指定無しに他クラスの静的メンバーの呼び出しを行えるようになった。
利用するにはusing static
の後に完全修飾なクラス名を指定する。
using static System.Math;
// ↑ソースコードの上部で宣言
class Hogehoge {
// System.Math.Pow() , System.Math.PI を修飾無しで呼び出す
double area = Pow(radius, 2) * PI;
}
例外フィルタ
catch
の後にwhen
キーワードを使用することで、処理する例外を限定することができるようになった。
try {
// ...
}
catch (AggregateException ex) when (ex.InnerException is ArgumentException) {
// ...
}
C# 7.0からの仕様
- 出力変数宣言
- パターンマッチング (is 式/switch 文)
- タプル (タプル記法/分解/値の破棄)
- ローカル関数
- 数値リテラルの改善(桁セパレータ/バイナリリテラル)
- ref戻り値、ref変数
- 非同期戻り値型の汎用化
- Expression-bodied 機能の拡充(コンストラクタ/デストラクタ/get/set/add/remove)
- Throw 式
出力変数宣言
out
引数で値を受け取る場合、その場所で変数宣言可能となった。
total += int.TryParse("123", out var num) ? num : 0;
パターンマッチング
is 式の拡張
is
式の構文が拡張され、型の後ろに変数名を宣言できるようになった。
拡張されたis
式はマッチした場合に宣言した変数にキャストした値を代入し、さらにtrueと評価される。
マッチしなかった場合はfalseと評価され、宣言した変数は未初期化状態となる。
void CheckAndSquare(object obj) {
// objの型チェックと同時にnumに値を代入する。
if (obj is int num && num >= 0) {
num = num * num;
}
else {
num = 0;
}
// if文の条件セクションは、ifの外側と同じスコープ
Console.WriteLine(num);
}
switch 文の拡張
switch
文のマッチ方法が拡張され、case
ラベルに従来の「定数パターン」に加え、新たに「型パターン」を指定できるようになった。
また、「型パターン」のcase
ラベルでは、when
句に条件を指定することができる。
「型パターン」を含むswitch
文では、必ずしも条件が排他的でなくなったため、最初にマッチしたcase
ラベルの処理が実行される。
[15]
void Decide(object obj) {
switch (obj) {
case int num when num < 0:
Console.WriteLine($"{num}は負の数です。");
break;
case int num:
Console.WriteLine($"{num}を二乗すると{num * num}です。");
break;
case "B":
Console.WriteLine($"これはBです。");
break;
case string str when str.StartsWith("H"):
Console.WriteLine($"{str}はHから始まる文字列です。");
break;
case string str:
Console.WriteLine($"{str}は文字列です。");
break;
case null:
Console.WriteLine($"nullです");
break;
default:
Console.WriteLine("判別できませんでした");
break;
}
}
タプル
タプルのための軽量な構文が導入された。
従来のSystem.Tuple
クラスとは別に、System.ValueTuple
構造体が新しく追加された。
タプル記法
2個以上の要素を持つタプルのための記法が導入された。 引数リストと同様の形式で、タプルを記述できる。
// タプル記法
(int, string) tuple = (123, "Apple");
Console.WriteLine($"{tuple.Item1}個の{tuple.Item2}");
分解
多値戻り値を簡単に扱えるように、分解がサポートされた。
var tuple = (123, "Apple");
// 分解
(int quantity, string name) = tuple;
Console.WriteLine($"{quantity}個の{name}");
分解はタプルに限らない。Deconstruct()
メソッドが定義されたクラスでも、分解を利用できる。
以下に、DateTime
型に分解を導入する例を示す。
static class DateExt {
public static void Deconstruct(this DateTime dateTime, out int year, out int month, out int day) {
year = dateTime.Year;
month = dateTime.Month;
day = dateTime.Day;
}
}
上記のコードでDateTime
型にDeconstruct()
拡張メソッドを定義し、
// 分解
(int year, int month, int day) = DateTime.Now;
のように左辺で3つの変数に値を受け取ることができる。
値の破棄
分解、out引数、パターンマッチングで、値の破棄を明示するために_
が利用できるようになった。
破棄された値は、後で参照することはできない。
// 年と日は使わない
(_, int month, _) = DateTime.Now;
// 解析結果だけ取得し、変換された値は使わない
bool isNumeric = int.TryParse(str, out _);
switch (obj) {
// string型で分岐するが、値は使わない
case string _:
// Do something.
break;
}
ref戻り値、ref変数
ref
キーワードの使用方法が拡張された。
これによって、安全な参照の使い道が広がった。
ref戻り値
戻り値の型をref
で修飾することで、オブジェクトの参照を戻り値とすることができる。
// 二つの参照引数の内、値の大きいものの参照戻り値を返す
static ref int Max(ref int left, ref int right) {
if (left >= right) {
return ref left;
}
else {
return ref right;
}
}
変数の寿命は変わらないため、メソッド終了時に破棄されるローカル変数をref戻り値とすることはできない。
static int s_count = 1;
// メンバーの参照はref戻り値になる。
static ref int ReturnMember() {
return ref s_count;
}
// ref引数はもちろんref戻り値になる。
static ref int ReturnRefParam(ref int something) {
return ref something;
}
// ローカル変数をref戻り値とすることはできない。
// static ref int ReturnLocal() {
// int x = 1;
// return ref x;
// }
ref変数
ローカル変数の型をref
で修飾することで、参照を代入することができる。
// 参照戻り値を参照変数で受け取る
ref int max = ref Max(ref x, ref y);
// limitとmaxは同じ値を参照する
ref int limit = ref max;
C# 7.1からの仕様
非同期なMainメソッド
Mainメソッドの戻り値として、Task
型、Task(int)
型が認められた。
static Task Main()
static Task<int> Main()
default式
型推論可能な場面では、default
の型指定は省略可能となった。
int number = default;
string name = default;
C# 7.2からの仕様
C#7.2で追加された仕様は以下の通り。 [16] [17]
値型の参照セマンティクス
値型におけるパフォーマンス向上を意図した複数の機能が追加された。
in参照渡し、ref readonly参照戻り値
引数にin
を指定することで、読み取り専用参照渡しを指定できる。
また、戻り値にref readonly
を指定することで、読み取り専用参照戻り値を指定できる。
これにより、構造体のコピーを避けると共に、意図しない値の変更を抑止できる。
readonly構造体
構造体宣言時にreadonly
を指定することで、真の読み取り専用構造体を定義できる。
readonly構造体の全てのフィールドはreadonly
でなければならず、this
ポインタも読み取り専用となる。
これにより、メンバーアクセス時の意図しない防御的コピーを抑止できる。
ref構造体
構造体宣言時にref
を指定することで、ヒープ領域へのコピーを防ぐ構造体がサポートされる。
ref構造体では、box化できない、配列を作成できない、型引数になることができない、など、ヒープ領域へのコピーを防ぐための厳しい制限がかかる。
この機能は、Span<T>
のような構造体をサポートするために利用され、unsafe文脈以外でのstackalloc
の利用をも可能とする。
末尾以外の場所での名前付き引数
C#4.0で追加された名前付き引数が末尾以外でも利用できるようになった。
Hogehoge(name: "John", 17);
private protected アクセス修飾子
同一アセンブリ内、かつ、継承先からのアクセス許可を表すprivate protected
アクセス修飾子が追加された。
数値リテラルの改善
十六進リテラルの0x
、二進リテラルの0b
の直後のアンダースコアが認められた。
int bin = 0b_01_01;
int hex = 0x_AB_CD;
C# 7.3からの仕様
C#7.3では以下の仕様が追加された。 [18]
- ジェネリック型制約の種類の追加
System.Enum
,System.Delegate
unmanaged
(文脈キーワード)
unsafe class MyGenericsClass<T1,T2,T3>
where T1 : System.Enum
where T2 : System.Delegate
where T3 : unmanaged {
public MyGenericsClass(T1 enum1, T1 enum2, T2 func, T3 unmanagedValue) {
if (enum1.HasFlag(enum2)) {
func.DynamicInvoke();
}
else {
T3* ptr = &unmanagedValue;
}
}
}
ref
ローカル変数の再割り当てstackalloc
初期化子- Indexing movable fixed buffers
- カスタム
fixed
ステートメント - オーバーロード解決ルールの改善
- 出力変数宣言の利用箇所の追加
class MyOutVar {
// メンバー変数初期化子やコンストラクタ初期化子で出力変数宣言が可能
readonly int x = int.TryParse("123", out var number) ? number : -1;
}
- タプル同士の比較
(long, long) tuple = (1L, 2L);
// タプルのすべての要素間で == が比較可能
if (tuple == (1, 2)) { }
// 要素数が異なるタプル同士は比較できない。
//if (tuple == (1, 2, 3)) { }
- バッキングフィールドに対するAttribute指定
// C#7.2までは無効な指定(コンパイル自体は可能。無視される)
// C#7.3からはバッキングフィールドに対するAttribute指定と見なされる
[field: NonSerialized]
public int MyProperty { get; set; }
C# 8.0からの仕様
C#8.0で追加された仕様は以下の通り。 [19]
null許容参照型
参照型にnull許容性を指定できるようになった。参照型の型名に?
を付加した場合にnull許容参照型となる。
参照型の型名に?
を付加しない場合、null非許容参照型となる。
フロー解析レベルでのnull許容性チェックが行われる。null許容値型のNullable<T>
のような新しい型は導入されない。
null許容コンテキスト
参照型のnull許容性は、null許容コンテキストによって有効、無効の切り替えが可能である。 C#7.3以前の互換性のために、既定では無効となっている。
- Nullable コンパイルオプション: プロジェクト全体でのnull許容コンテキストを指定する
#nullable
ディレクティブ: ソースコードの部分ごとにnull許容コンテキストを指定するannotations
オプション、warnings
オプションにより、適用範囲を限定できる
null免除演算子
null許容参照型の変数名の後に !
を使用することで、フロー解析時の警告が免除される。
インターフェイスの既定メンバー
インターフェイスのメンバーに既定の実装を指定できるようになった。また、インターフェイスに静的メンバーを持つことができるようになった。
さらに、インターフェイスのメンバーにアクセシビリティを指定できるようになった。
- 既定のアクセシビリティは、従来通り
public
となる。 - 実装があるインスタンスメンバーは、既定で
virtual
となりoverride
可能である。 - 実装を
override
させないためにsealed
を指定することができる。
パターンマッチングの拡張
switch
式が追加された。
プロパティパターン、タプルパターン、位置指定パターンの追加により、再帰的なパターンマッチングが可能になった。
- switch式
- 再帰パターン
- プロパティパターン
- タプルパターン
- 位置指定パターン
非同期ストリーム
IAsyncEnumerable<T>
インターフェイスを返すことで、イテレータ構文と非同期構文の共存が可能になった。
async IAsyncEnumerable<int> EnumerateAsync() {
await Task.Delay(100);
yield return 1;
await Task.Delay(100);
yield return 2;
}
await foreach
によって非同期ストリームを列挙する。
async void SpendAsync() {
await foreach (var item in EnumerateAsync()) {
Console.WriteLine(item);
}
}
範囲指定
Index
とRange
を指定できる専用構文が追加された。
Index a = 1; // new Index(1, fromEnd: false)
Index b = ^1; // new Index(1, fromEnd: true)
Range range = a..b; // new Range(start: a, end: b)
その他の仕様
- 静的ローカル関数
- null結合代入演算子
- 構造体の読み取り専用メンバー
- using 宣言
- ref構造体のDispose
- ジェネリクスを含むアンマネージ型
- 式中の
stackalloc
- 文字列補間のトークン順序の緩和
実装
C#の言語仕様は標準化団体Ecma Internationalを通じて公開・標準化されており、第三者がマイクロソフトとは無関係にコンパイラや実行環境を実装することができる[1][20]。 現段階で、C#コンパイラの実装は次の5つが知られている。
- マイクロソフト製
- Visual Studio 2015 以降で使用されている、.NETコンパイラプラットフォーム (コードネームRoslyn)。ApacheライセンスのオープンソースプロジェクトでGitHubで公開されている[21]。Windows、macOS、Linuxで動作する。C#のコンパイラはC#、VB.NETのコンパイラはVB.NETで実装されている。以前のコンパイラと比べて、リファクタリングやIDE、スクリプティングなどへの利用が可能なAPIが公開されており、コンパイラ以外への様々な応用が可能。
- Visual Studio 2013 まで使われていた、マイクロソフトによるVisual C# コンパイラ。
- 2006年のC# 2.0当時の、マイクロソフトによるShared Source Common Language Infrastructure。共通言語基盤 (CLI) とC#コンパイラがソースコードで公開されている。
- Mono ProjectによるMono内の Mono Compiler Suite (mcs)。
- 2012年まで開発されていた、DotGNU Projectによる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) と書く[22]。 音楽のシャープ (♯, MUSIC SHARP SIGN (U+266F)) ではなくナンバーサイン (#) を採用したのは、フォントやブラウザなどの技術的な制約に加え、ASCIIコードおよび標準的キーボードには前者の記号が存在しないためである。
- "#"接尾辞は、他の.NET言語にも使用されており、J#(Javaのマイクロソフトによる実装)、A#(Adaから)、F#(System Fなどから[23])が含まれる。また"#"接尾辞はGtk#(GTK+などのGNOMEライブラリの.NETラッパ)、Cocoa#(Cocoaのラッパ)などのライブラリにも使用されている。そのほか、SharpDevelopなどの"Sharp"を冠する関連ソフトウェアも存在する。
- C#という名称の解釈として、「(A-Gで表された)直前の音を半音上げる」という音楽記号の役割に着目し、「C言語を改良したもの」を意味したのではないか、というものがある[要出典]。これは、C++の名称が「C言語を1つ進めたもの」という意味でつけられたことにも似ている。
- アンダース・ヘルスバーグは、「C#」が「C++++」(すなわち「C++をさらに進めたもの」)にみえるのが由来である、と語っている[24][25]。
脚注
注釈
出典
- ^ a b Abel Avram (2009年7月29日). “誰でもC#とCLIの正式な実装が可能に”. InfoQ. 2019年12月2日閲覧。
- ^ “Standard ECMA-334”. ECMA. 2019年6月22日閲覧。
- ^ Standard ECMA-334-archive
- ^ “Using C# 3.0 from .NET 2.0”. Danielmoth.com (May 13, 2007). October 4, 2012閲覧。
- ^ “Visual Studio 2017 15.3 Release Notes”. Microsoft Docs. 2018年11月12日閲覧。
- ^ Richard Lander (2018年5月30日). “Announcing .NET Core 2.1” (英語). .NET Blog. Microsoft. 2019年12月2日閲覧。
- ^ “Visual Studio 2017 15.5 Release Notes”. Microsoft Docs. 2018年11月12日閲覧。
- ^ “Visual Studio 2017 15.7 Release Notes”. Microsoft Docs. 2018年8月24閲覧。
- ^ Richard Lander (2019年9月23日). “Announcing .NET Core 3.0” (英語). .NET Blog. Microsoft. 2019年12月2日閲覧。
- ^ “Visual Studio 2019 Release Notes”. Microsoft Docs. 2019年9月30日閲覧。
- ^ 2-2 変数と定数 | MSDN
- ^ .NET での文字エンコード | Microsoft Docs
- ^ null 許容型のボックス化 (C# プログラミング ガイド) MSDNライブラリ
- ^ 拡張メソッド (C# プログラミング ガイド) | Microsoft Docs
- ^ https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/switch
- ^ https://blogs.msdn.microsoft.com/dotnet/2017/11/15/welcome-to-c-7-2-and-span/
- ^ https://www.visualstudio.com/en-us/news/releasenotes/vs2017-relnotes#compiler
- ^ https://docs.microsoft.com/ja-jp/visualstudio/releasenotes/vs2017-relnotes#csharp
- ^ https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8
- ^ Tim Smith (2010年10月4日). “Javaと.NETの特許問題への短い紹介”. InfoQ. 2019年12月2日閲覧。
- ^ dotnet/roslyn - GitHub
- ^ Standard ECMA-334 C# Language Specification
- ^ The A-Z of programming languages: F# | Network World
- ^ レポート:コミュニティスペシャルセッション with Anders Hejlsberg in Microsoft Developers Conference 2006
- ^ C#への期待。アンダースからの返答
関連項目
- 言語仕様
- 実行環境
- 開発環境
- Visual C#
- Visual Studio
- SharpDevelop - フリーの統合開発環境
- 比較
外部リンク
- C# - Microsoft Docs
- 言語仕様
- C# 言語仕様 - Microsoft Docs
- ECMA-334 C# 言語仕様
- JIS X 3015:2008「プログラム言語C#」(日本産業標準調査会、経済産業省)
- Mono C# コンパイラ
- Microsoft Shared Source Common Language Infrastructure 2.0 Release