C Sharp

C#(シーシャープ)は、Microsoft社が開発した、汎用のマルチパラダイムプログラミング言語である。C#は、Javaに似た構文を持ち、C++に比べて扱いやすく、プログラムの記述量も少なくて済む。また、C#は、.NET Framework上で動作することを前提として開発された言語であり、WindowsアプリケーションやWebアプリケーションの開発に適している。

C#
C#
C#のロゴ
パラダイム 構造化プログラミング命令型プログラミングオブジェクト指向プログラミングイベント駆動型プログラミング関数型プログラミングジェネリックプログラミングリフレクションクラスベース、正格プログラミング、マルチパラダイムプログラミング ウィキデータを編集
登場時期 2000年 (2000)
設計者 マイクロソフトアンダース・ヘルスバーグ率いるチーム)
開発者 マイクロソフト ウィキデータを編集
最新リリース 11.0/ 2022年11月8日 (2022-11-08)[1]
型付け 強い静的型付け(4.0から動的型導入)
主な処理系 CLR, Mono
影響を受けた言語 C++C言語Java、Delphi、Modula-3EiffelF SharpHaskellIconJ SharpMicrosoft Visual J++Object PascalRustMLVisual Basic ウィキデータを編集
影響を与えた言語 D言語, F#, Java, Nemerle, Vala
プラットフォーム Windows, macOS, Linuxなど
ライセンス Apacheライセンス (Roslyn)
ウェブサイト docs.microsoft.com/ja-jp/dotnet/csharp/
拡張子 cs、csx ウィキデータを編集

マルチパラダイムをサポートする汎用高レベルプログラミング言語で、静的型付けタイプセーフスコープ命令型宣言型関数型汎用型オブジェクト指向クラスベース)、コンポーネント指向のプログラミング分野を含んでいる。

共通言語基盤 (CLI) といった周辺技術も含め、Microsoft社のフレームワークである「.NET Framework」の一部である。また、以前のVisual J++で「非互換なJava」をJavaに持ち込もうとしたMicrosoft社とは異なり、その多くの[注釈 1]仕様を積極的に公開し、標準化機構に託して自由な利用を許す (ECMA-334,ISO/IEC 23270:2003,JIS X 3015) など、同社の姿勢の変化があらわれている

設計はデンマークのアンダース・ヘルスバーグによる。

構文はC系言語(C,C++など)の影響を受けており、その他の要素には以前ヘルスバーグが所属していたボーランド設計のDelphiの影響が見受けられる。

概要

開発にはボーランドのTurbo PascalDelphiを開発したアンダース・ヘルスバーグを筆頭として多数のDelphi開発陣が参加している。

C#は共通言語基盤共通言語ランタイムなど)が解釈する共通中間言語コンパイルされて実行される。

自動ボックス化デリゲートプロパティインデクサカスタム属性ポインタ演算操作、構造体(値型オブジェクト)、多次元配列可変長引数、などの機能を持つ。また、Javaと同様に大規模ライブラリプロセッサ・アーキテクチャに依存しない実行形態、ガベージコレクションJITコンパイルによる実行の高速化、などが実現されている(もっともこれらはC#の機能というより.NET Frameworkによるものである)。

.NET構想における中心的な開発言語であり、XML WebサービスASP.NETの記述にも使用される。他の.NET系の言語でも記述可能だが、生産性・機能においてC#が最も優れるとされる。マイクロソフトの統合開発環境では、Microsoft Visual C#がC#に対応している。

共通言語仕様のCLSによって、他のCLS準拠の言語(Visual Basic .NETVisual C++ (C++/CLI) など)と相互に連携することができる。

バージョンおよびリリース時期

バージョン 言語仕様 リリース時期 .NET Visual Studio
ECMA[3][4] 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)[a 1]
2003年10月 2003年4月 .NET Framework 1.1[b 1] .NET 2003[a 1][b 1]
2.0

ECMA-334:2006 (2006年6月)

ISO/IEC 23270:2006 (2006年9月)

2005年9月 2005年11月
  • .NET Framework 2.0[b 2]
  • .NET Framework 3.0
2005[a 1]
3.0 N/A N/A 2007年8月 2007年11月
2008[a 1]
4.0 N/A N/A 2010年4月 2010年4月 .NET Framework 4[b 3] 2010[a 1]
5.0

ECMA-334:2017 (2017年12月)

ISO/IEC 23270:2018 (2018年12月)

2013年6月 2012年8月 .NET Framework 4.5[b 4]
6.0

ECMA-334:2022 (2022年6月)

N/A Draft 2015年7月
  • .NET Framework 4.6[6]
  • .NET Core 1.0
  • .NET Core 1.1
2015[a 1]
7.0 未定 未定 N/A 2017年3月 .NET Framework 4.7[7] 2017 version 15.0[a 2]
7.1 N/A N/A N/A 2017年8月 .NET Core 2.0[a 1] 2017 version 15.3[a 3]
7.2 N/A N/A N/A 2017年11月 .NET Core 2.0[a 1] 2017 version 15.5[a 4]
7.3 N/A N/A N/A 2018年5月
  • .NET Core 2.1[b 5]
  • .NET Core 2.2
  • .NET Framework 4.8
2017 version 15.7[a 5]
8.0 N/A N/A N/A 2019年9月
  • .NET Core 3.0[8]
  • .NET Core 3.1
2019 version 16.3[a 6]
9.0 N/A N/A N/A 2020年11月 .NET 5.0[9] 2019 version 16.8[a 7]
10.0[a 8] N/A N/A Proposal 2021年12月
  • .NET 6.0
  • .NET 6.0.1
2022 version 17.0[a 9]

言語仕様

さまざまな意味において、基盤である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に準拠する[a 10]。文字および文字列はUTF-16エンコーディングを採用する[a 11]

ポインタとメモリ管理

  • ポインタをサポートする。ポインタは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) が導入された[a 1]。以下のようにクラスや構造体の宣言にpartial修飾子をつけることで、その宣言を分割することができる。

partial class MyClass { int a; }
partial class MyClass { int b; }

これは以下と同義である:

class MyClass { int a; int b; }

これによって、巨大なクラスを分割したり、自動生成されたコードを分離したりすることができる。partial 修飾子はすべての宣言につける必要がある。

[10]

ジェネリクス

ジェネリクスが導入された[a 1]。これは.NET Framework 2.0の機能である。クラス、構造体、インタフェース、デリゲート、メソッドに対して適用することができる。.NETのGenericsはC++のテンプレート、あるいはJavaにおけるそれとも異なるもので、コンパイルによってではなく実行時にランタイムによって特殊化される。これによって異なる言語間の運用を可能にし、リフレクションによって型パラメーターに関する情報を取得することができる。また、where節によって型パラメーターに制約を与えることができる。一方、C++のように型パラメーターとしてを指定することはできない。なお、ジェネリックメソッドの呼び出し時に引数によって型パラメーターが推論できる場合、型パラメーターの指定は省略できる。

[11]

静的クラス

静的クラスが導入された[a 1]static属性をクラスの宣言につけることで、クラスはインスタンス化できなくなり、静的なメンバーしか持つことができなくなる。

[10]

イテレータ

イテレータ#C# 2.0を参照。

[12]

yieldキーワード

yieldキーワードによるコルーチンを使うことで、イテレータの生成を楽に実装できるようになった。

匿名デリゲート

クロージャの機能を提供する匿名デリゲートが導入された。

プロパティに対する個別のアクセス制御

Property Accessors プロパティのget もしくは setアクセサのどちらかにアクセス修飾子を指定することでアクセス制御が別個にできるようになった[a 1]。次の例では、getアクセサはpublicsetアクセサはprivateである。

public class MyClass
{
  private string status = string.Empty;
  public string Status
  {
    get { return status; }
    private set { status = value; }
  }
}

[13]

Null許容型とnull結合演算子

nullを保持できる値型、Nullableが導入された[a 1]

int? i = 512;
i = null;

int? j = i + 500; //jはnullとなる。nullとの演算の結果はnullになる。

int?Nullable<int>糖衣構文である。また、nullを保持しているNull許容型のインスタンスをボックス化しようとすると、単に空参照 (null) に変換される[14]

int? x = null;
object o = x;
System.Console.WriteLine(o == null); //Trueが出力される

[15]

また、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型に代入することはできない

その他

varキーワード

var キーワードが導入され、型推論を利用したローカル変数の宣言ができるようになった[a 1]

var s = "foo";
// 上の文は右辺が string 型であるため、次のように解釈される:
string s = "foo";
// 以下に挙げる文は誤りである(コンパイルエラーとなる):
var v; // 初期化式を欠いている (型を推論する対象が存在しない)
var v = null; // 型が推論できない (曖昧である)

拡張メソッド

拡張メソッド (extension method) が導入された[a 1]。既存のクラスを継承して新たなクラスを定義することなく、新たなインスタンスメソッドを疑似的に追加定義することができる。具体的には、入れ子になっていない、非ジェネリックの静的クラス内に、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

拡張メソッドは糖衣構文の一種であり、カプセル化の原則に違反するものではないが、必要な場合に限り注意して実装することがガイドラインとして推奨されている[a 12]

部分メソッド

部分メソッドが導入された[a 1]。部分型(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. と表示される。

ラムダ式

ラムダ式が導入された[a 1]。この名前はラムダ計算に由来する。

以下の匿名メソッド

// 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 と命名している。実際はプログラマはこの変数にアクセスすることはできない。

自動実装プロパティ

プロパティをより簡潔に記述するための自動実装プロパティが導入された[a 1]。プロパティの定義に 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 と命名している。実際はプログラマはこのフィールドにアクセスすることはできない。

匿名型

一時的に使用される型を簡単に定義するための匿名型が導入された[a 1]。以下に例を挙げる:

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 をサポートするために、クエリ式が導入された[a 1]。これは 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の強力なインテリセンスが利用できることを理由に、著者はクエリ構文をほとんど使用していないと記している。

dynamicキーワード

dynamicキーワードが導入され、動的型付け変数を定義できるようになった[a 1]。dynamic型として宣言されたオブジェクトに対する操作のバインドは実行時まで遅延される。

// xはint型と推論される:
var x = 1; 
// yはdynamic型として扱われる:
dynamic y = 2; 

public dynamic GetValue(dynamic obj)
{
  // objにValueが定義されていなくとも、コンパイルエラーとはならない:
  return obj.Value;
}

オプション引数・名前付き引数

VBC++に実装されているオプション引数・名前付き引数が、C#でも利用できるようになった[a 1]

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修飾子を指定することにより、ジェネリクスの共変性・反変性を指定できるようになった[a 1]

IEnumerable<string> x = new List<string> { "a", "b", "c" };
// IEnumerable<T>インターフェイスは型引数にout修飾子が指定されているため、共変である。
// したがって、C# 4.0では次の行はコンパイルエラーにならない
IEnumerable<object> y = x;

C# 5.0からの仕様

C# 6.0からの仕様

  • 自動実装プロパティの初期化子[a 13]
  • get のみの自動実装プロパティおよびコンストラクタ代入[a 13]
  • 静的 using ディレクティブ[a 13]
  • インデックス初期化子[a 13]
  • catch/finally での await[a 13]
  • 例外フィルタ[a 13]
  • 式形式のメンバー (expression-bodied members)[a 13]
  • null条件演算子[a 13]
  • 文字列補間(テンプレート文字列)[a 13]
  • nameof 演算子[a 13]
  • #pragma
  • コレクションの初期化子での拡張メソッド[a 13]
  • オーバーロード解決の改善[a 13]

静的 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引数で値を受け取る場合、その場所で変数宣言可能となった[a 14]

total += int.TryParse("123", out var num) ? num : 0;
is 式の拡張

is式の構文が拡張され、型の後ろに変数名を宣言できるようになった[a 14]。 拡張された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ラベルの処理が実行される。[a 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;
    }
}

タプル

タプルのための軽量な構文が導入された[a 14]。従来のSystem.Tupleクラスとは別に、System.ValueTuple構造体が新しく追加された。

タプル記法

2個以上の要素を持つタプルのための記法が導入された。 引数リストと同様の形式で、タプルを記述できる。

// タプル記法
(int, string) tuple = (123, "Apple");
Console.WriteLine($"{tuple.Item1}個の{tuple.Item2}");
分解

多値戻り値を簡単に扱えるように、分解がサポートされた[a 14]

var tuple = (123, "Apple");
// 分解
(int quantity, string name) = tuple;
Console.WriteLine($"{quantity}個の{name}");

分解はタプルに限らない。Deconstruct()メソッドが定義されたクラスでも、分解を利用できる[a 14]

以下に、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;

非同期なMainメソッド

Mainメソッドの戻り値として、Task型、Task(int)型が認められた[a 16]

static Task Main()
static Task<int> Main()

default式

型推論可能な場面では、defaultの型指定は省略可能となった[a 16]

int number = default;
string name = default;

C# 7.2からの仕様

C#7.2で追加された仕様は以下の通り[a 17][19]

値型の参照セマンティクス

値型におけるパフォーマンス向上を意図した複数の機能が追加された。

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では以下の仕様が追加された[a 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で追加された仕様は以下の通り。[a 19][20]

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);
    }
}

範囲指定

IndexRangeを指定できる専用構文が追加された。

  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# 9.0からの仕様

C# 9.0で追加された仕様は以下の通り。

[a 20]

  • レコード
  • プロパティのinitアクセサ
  • 最上位レベルステートメント
  • パターンマッチングの拡張
  • new式の型推論
  • 条件演算子の型推論
  • 共変戻り値
  • GetEnumeratorの拡張メソッド対応
  • 静的匿名関数
  • ラムダ式引数の破棄
  • ローカル関数への属性適用
  • パフォーマンスと相互運用
    • ネイティブサイズの整数型(nint nuint型)
    • 関数ポインタ(delegate*型)
    • 変数初期化フラグの抑制
  • コードジェネレータのサポート
    • モジュール初期化子
    • 部分メソッドの拡張

C# 10.0からの仕様

C# 10.0で追加された仕様は以下の通り。

[a 21]

  • レコード構造体
  • 構造体型の機能強化
  • 補間された文字列ハンドラー
  • global using ディレクティブ
  • ファイル スコープの名前空間の宣言
  • 拡張プロパティのパターン
  • ラムダ式の機能強化
  • const 補間文字列を許可する
  • レコードの型で ToString() を封印できる
  • 限定代入の機能強化
  • 同じ分解で代入と宣言の両方を許可する
  • メソッドで AsyncMethodBuilder 属性を許可する
  • CallerArgumentExpression 属性
  • 拡張 #line pragma
  • 警告ウェーブ 6

実装

C#の言語仕様は標準化団体Ecma Internationalを通じて公開・標準化されており、第三者がマイクロソフトとは無関係にコンパイラや実行環境を実装することができる[2][21]。 現段階で、C#コンパイラの実装は次の5つが知られている。

  • マイクロソフト製
    • Visual Studio 2015 以降で使用されている、.NETコンパイラプラットフォーム (コードネームRoslyn)。ApacheライセンスオープンソースプロジェクトでGitHubで公開されている[22]WindowsmacOSLinuxで動作する。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) と書く[23]。 音楽のシャープ (♯, MUSIC SHARP SIGN (U+266F)) ではなくナンバーサイン (#) を採用したのは、フォントブラウザなどの技術的な制約に加え、ASCIIコードおよび標準的キーボードには前者の記号が存在しないためである。
  • "#"接尾辞は、他の.NET言語にも使用されており、J#(Javaのマイクロソフトによる実装)、A#Adaから)、F#System Fなどから[24])が含まれる。また"#"接尾辞はGtk#GTKなどのGNOMEライブラリの.NETラッパ)、Cocoa#Cocoa (API)のラッパ)などのライブラリにも使用されている。そのほか、SharpDevelopなどの"Sharp"を冠する関連ソフトウェアも存在する。
  • C#という名称の解釈として、「(A-Gで表された)直前の音を半音上げる」という音楽記号の役割に着目し、「C言語を改良したもの」を意味したのではないか、というものがある。これは、C++の名称が「C言語を1つ進めたもの」という意味でつけられたことにも似ている。
  • アンダース・ヘルスバーグは、「C#」が「C++++」(すなわち「C++をさらに進めたもの」)にみえるのが由来である、と語っている[25][26]

注釈

  1. 全てではなく一部にプロプライエタリなコンポーネントもある。そのため、それに依存するものなど、後述のMonoなどで制限がある場合がある[2]
  2. (LINQを除く)[5]

出典

  1. Welcome to C# 11”. マイクロソフト (2022年11月8日). 2022年11月23日閲覧。
  2. Abel Avram (2009年7月29日). 誰でもC#とCLIの正式な実装が可能に”. InfoQ. 2019年12月2日閲覧。
  3. Standard ECMA-334”. ECMA. 2022年6月28日閲覧。
  4. Standard ECMA-334-archive”. 2018年11月13日時点のオリジナルよりアーカイブ。2018年11月13日閲覧。
  5. Using C# 3.0 from .NET 2.0”. Danielmoth.com (2007年5月13日). 2012年10月4日閲覧。
  6. “Microsoft、「.NET Framework 4.6」を正式公開”. 窓の杜. https://forest.watch.impress.co.jp/docs/news/712658.html 2021年1月23日閲覧。
  7. “.NET Framework 4.7が一般公開される”. InfoQ. https://www.infoq.com/jp/news/2017/05/net47-released/ 2021年1月23日閲覧。
  8. “Micorsoftが.NET Core 3.0をリリース”. InfoQ. https://www.infoq.com/jp/news/2019/12/microsoft-releases-dotnet-core-3/ 2021年1月23日閲覧。
  9. Richard Lander (2020年11月10日). Announcing .NET 5.0 (英語). .NET Blog. Microsoft. 2020年11月11日閲覧。
  10. 高橋 2005, p. 70.
  11. 高橋 2005, pp. 63–64.
  12. 高橋 2005, pp. 68–70.
  13. 高橋 2005, pp. 70, 71.
  14. null 許容型のボックス化 (C# プログラミング ガイド) (pdf). Microsoft. 2008年6月2日閲覧。
  15. 高橋 2005, p. 68.
  16. 高橋 2005, pp. 66, 67.
  17. 高橋 2005, p. 71.
  18. 高橋 2005, p. 72.
  19. Mads Torgersen (2017年11月15日). Welcome to C# 7.2 and Span (英語). .NET Blog. Microsoft. 2017年11月23日閲覧。
  20. MicrosoftがC# 8.0をリリース”. InfoQ (2019年12月10日). 2019年12月12日閲覧。
  21. Tim Smith (2010年10月4日). Javaと.NETの特許問題への短い紹介”. InfoQ. 2019年12月2日閲覧。
  22. dotnet/roslyn - GitHub
  23. Standard ECMA-334 C# Language Specification
  24. The A-Z of programming languages: F# | Network World
  25. レポート:コミュニティスペシャルセッション with Anders Hejlsberg in Microsoft Developers Conference 2006
  26. C#への期待。アンダースからの返答

公式発表

  1. C# の歴史”. Microsoft Docs. 2019年12月12日閲覧。
  2. Visual Studio 2017 バージョン 15.0 リリース ノート”. Microsoft Docs. 2021年1月23日閲覧。
  3. Visual Studio 2017 15.3 Release Notes”. Microsoft Docs. 2018年11月12日閲覧。
  4. Visual Studio 2017 15.5 Release Notes”. Microsoft Docs. 2018年11月12日閲覧。
  5. Visual Studio 2017 15.7 Release Notes”. Microsoft Docs. 2018年8月24閲覧。
  6. Visual Studio 2019 Release Notes”. Microsoft Docs. 2019年9月30日閲覧。
  7. Visual Studio 2019 Release Notes”. Microsoft Docs. 2020年11月10日閲覧。
  8. What's new in C# 10 (英語). docs.microsoft.com. 2022年6月28日閲覧。
  9. Visual Studio 2022 version 17.0 Release Notes”. docs.microsoft.com. 2022年6月28日閲覧。
  10. 2-2 変数と定数”. Microsoft Docs. 2018年11月11日閲覧。
  11. .NET での文字エンコード”. Microsoft Docs. 2018年11月11日閲覧。
  12. 拡張メソッド (C# プログラミング ガイド)”. Microsoft Docs. 2018年11月10日閲覧。
  13. C# 6 の新機能”. Microsoft Docs. 2019年12月12日閲覧。
  14. C# 7.0 の新機能”. Microsoft Docs. 2019年12月12日閲覧。
  15. switch (C# リファレンス)”. Microsoft Docs. 2017年9月10日閲覧。
  16. C# 7.1 の新機能”. Microsoft Docs. 2019年12月12日閲覧。
  17. C# 7.2 の新機能”. Microsoft Docs. 2019年12月12日閲覧。
  18. C# 7.3 の新機能”. Microsoft Docs. 2019年12月12日閲覧。
  19. C# 8.0 の新機能”. Microsoft Docs. 2019年12月12日閲覧。
  20. What's new in C# 9.0”. Microsoft Docs. 2021年10月17日閲覧。
  21. What's new in C# 10.0”. Microsoft Docs. 2022年11月3日閲覧。

個人サイト

参考文献

  • 高橋 忍「C# 2.0の新しい言語仕様」『C MAGAZINE(2005年2月号)』第17巻第2号、ソフトバンク パブリッシング。
  • 山田祥寛『独習C#』(第5版)翔泳社〈独習〉、2022年7月21日。ISBN 978-4-7981-7556-0。

関連項目

外部リンク

This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.