C Sharp

概要

開発にはボーランドの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) など）と相互に連携することができる。

言語仕様

さまざまな意味において、基盤であるCLIの機能をもっとも反映している言語であるといえる。C#にある組み込み型のほとんどは、CLIフレームワークに実装されている値型と対応している。しかし、C#の言語仕様はコンパイラのコード生成については何も言及していない。つまり、CLRに対応しなければならないとか、共通中間言語 (CIL) などの特定のフォーマットのコードを生成しなければならないとかいうことは述べられていない。そのため、理論的にはC++やFORTRANのように環境依存のマシン語を生成することも可能である。しかし、現在存在するすべてのC#コンパイラはCLIをターゲットにしている。

CやC++からの改良点

C#では、CやC++と比較してさまざまな制限や改良が加えられている。また、仕様の多くはC#言語というよりは、基盤である.NET Frameworkそのものに依拠している。Javaで導入された制限および改良をC#でも同様に採用しているものが多いが、C#で新たに導入された改良がのちにJavaにも同様に採用されたものもある。その例を次に挙げる。

部分型

部分型 (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) に変換される。
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型に代入することはできない

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

部分メソッド

部分メソッドが導入された。部分型（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の強力なインテリセンスが利用できることを理由に、著者はクエリ構文をほとんど使用していないと記している。

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;

静的 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) {
    // ...}

出力変数宣言

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ラベルの処理が実行される。
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;

非同期な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で追加された仕様は以下の通り。

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

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

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;