C Sharpその他 - C Sharpのバージョン
概要
ここでは、C# 4.0以降におけるバージョンごとの新機能について記載する。
下図に、Visual StudioとC#、.NET Frameworkの関係を示す。
.NET Framework 2.0以降のバージョンアップにおいて、多くがライブラリの追加のみといえる。
- .NET Framework 1.1 … バグ修正のみ
- .NET Framework 2.0 … ジェネリックなコレクション追加
- .NET Framework 3.0 … WPF、WCF、WF 等追加
- .NET Framework 3.5 … LINQ 関連のライブラリ、ASP.NET AJAX 追加
- .NET Framework 3.5 SP1 … ADO.NET Entity Framework 等追加
- .NET Framework 4 … dynamic 関連、並列処理関連の追加
- .NET Framework 4.5 … async/await 関連の追加
.NET Framework 3.0〜3.5 SP1のバージョンアップが細かい理由として、新機能をできるだけ早く提供してほしいという要望のためである。
.NET Framework 4以降は、NuGetパッケージマネージャ等が誕生したこともあり、.NET Frameworkとは別にライブラリ提供するようになった。
.NET 5(.NET Frameworkは廃止)では、NuGetパッケージマネージャによる配布が本格化しており、.NET 5ならではの機能はもはや明確ではない。
C# 4.0の新機能
- リリース時期
- 2010/4
- 同世代技術
- Visual Studio 2010
- .NET Framework 4
- Visual Basic 10
- 要約・目玉機能
- dynamic
- 相互運用
C# 4.0で追加される機能は以下の3つである。
- 動的型付け変数
- オプション引数 / 名前付き引数
- ジェネリクスの共変性・反変性
動的型付け変数 : dynamicキーワード
dynamicキーワードを使用することで、動的型付け変数を定義できる。
この機能は、コンパイル時に確定しないプロパティアクセスやメソッドの呼び出しができる。
主な使用用途において、スクリプト言語との連携やDLLの遅延バインディングのために使用する。
記述方法は、var(型推論)と似ているが、dynamicキーワードで宣言した変数の型は動的型になる。
var sx = 1; // int型
dynamic dx = 1; // dynamic型
C# 3.0以前のような静的型付け言語では、オブジェクトがどのような名前のプロパティやメソッドを持っているかをコンパイル時に知る必要がある。
以下の例では、'object' に 'X' の定義が含まれていません
というエラーが発生する。
static object GetX(object obj)
{
return obj.X;
}
C# 4.0以降では、dynamic型を使用することで、以下のように記述できる。
変数objがXというプロパティを持っているかは、プログラム実行時に調べる。
static dynamic GetX(dynamic obj)
{
return obj.X;
}
詳細を知りたい場合は、dynamicを参照すること。
オプション引数 / 名前付き引数
C# 4.0以降では、C++やVB.NET等に存在するオプション引数と名前付き引数が使用できる。
オプション引数
以下のように、規定値を持つメソッドを定義すると、引数の一部または全てを省略することができる。(オプション引数と呼ぶ)
この記法において、省略できるものは後の引数のみである。
static int Sum(int x = 0, int y = 0, int z = 0)
{
return x + y + z;
}
int s1 = Sum(); // Sum(0, 0, 0); と同じ
int s2 = Sum(1); // Sum(1, 0, 0); と同じ
int s3 = Sum(1, 2); // Sum(1, 2, 0); と同じ
名前付き引数
上記で定義した規定値を持つメソッドを、以下のような構文で呼ぶことができる。
名前付き引数は、引数の順番は自由に記述できる。
また、任意の箇所を省略することができる。
int s1 = Sum(x: 1, y: 2, z: 3); // Sum(1, 2, 3); と同じ
int s2 = Sum(y: 1, z: 2, x: 3); // Sum(3, 1, 2); と同じ
int s3 = Sum(y: 1); // Sum(0, 1, 0); と同じ
詳細を知りたい場合は、オプション引数・名前付き引数を参照すること。
ジェネリックの共変性と反変性
C# 4.0以降では、ジェネリクスの型引数に共変性と反変性を持つことができる。
ジェネリクスの共変性と反変性を実現するため、ジェネリクスの型引数に対して、in / outを指定する。
出力(戻り値またはプロパティのget)でのみ使用する場合は、型にoutを記述することで共変性が認められる。
public interface IEnumerable<out T> { ... }
IEnumerable<string> strings = new[] {"aa", "bb", "cc"};
IEnumerable<object> objs = strings;
// foreach (object x in strings) ってやっても問題ないんだから、
// objs に strings を代入しても OK。
入力(引数またはプロパティのset)でのみ使用する場合は、型にinを記述することで反変性が認められる。
public delegate void Action<in T> (T arg);
Action<object> objAction = x => { Console.Write(x); };
Action<string> strAction = objAction;
// objAction("string"); ってやっても問題ないんだから、
// strAction に objAction を代入しても OK。
詳細を知りたい場合は、ジェネリクスの共変性と反変性を参照すること。
COM相互運用時の特別処理
.NET FrameworkにはCOM相互運用機能があり、COMのクラスを.NET Frameworkのクラスであるかのように扱うことができる。
ただし、設計思想に大きな差があるので、.NET Frameworkでは不要だがCOM相互運用をする上では便利な機能がいくつか存在する。
C# 4.0以降では、COM相互運用のクラス(Runtime Callable Wrapper : .NET FrameworkからCOMを呼び出すためのラッパークラス)に対してのみ、
以下に示す特別な処理を行う。
- 引数の参照渡しにおいて、
ref
キーワードを付けなくても呼び出すことができる。- C#では、"引数の参照渡しは、呼び出し側からも参照渡しであることが一目で理解できる"ことが良いので、
ref
キーワードの省略は望ましくないが、 - COMの場合、参照渡しが不要と思われる引数も
ref
キーワードが付くので、ref
キーワードの省略を認めている。
- C#では、"引数の参照渡しは、呼び出し側からも参照渡しであることが一目で理解できる"ことが良いので、
get_X(index)
メソッド /set_X(index, value)
メソッドに対して、インデックス付きプロパティ構文(X[index]
という記述方法)が使用できる。- C#では、"インデックス付きプロパティではなく、インデクサ持ちの型のプロパティを作成する"という設計思想である。
- COMの全盛期では、インデックス付きプロパティが主流だったので、この構文も認められる。
- ただし、COMの
get_X(index)
をX[index]
で参照できるだけであり、C#において、インデックス付きプロパティが定義できるわけではない。 - また、VB.NETで記述したインデックス付きプロパティは、C#から呼ぶ場合は、
get_X(index)
/set_X(index, value)
という記述をする必要がある。
C# 5.0の新機能
- リリース時期
- 2012年8月
- 同世代技術
- Visual Studio 2012
- .NET Framework 4.5
- Visual Basic 11
- Windows Runtime
- .NET Core Profile(現在の.NET Coreの原型)
- 要約・目玉機能
- 非同期処理
.NET Framework 4以降、言語機能、ライブラリ、ツールは、拡張という形で小出しにされるようになった。
非同期処理
async / awaitキーワードを使用することで、非同期処理を簡潔に記述することができる。
非同期処理には、処理の内容をシーケンス図やフローチャートに起こすと、同期処理と同じになるものがある。
このような処理に対して、async / awaitを使用することにより、同期処理と同じように非同期処理を記述できる。
Caller Info属性
C++のFILEやLINEマクロに相当するデバッグ用診断を実現するための機能である。
以下のように、メソッドの引数に属性を付加することで、コンパイラはメソッドの引数に対して診断情報を渡す。
コンパイル結果には、定数やオプション引数と同様、コンパイル時のリテラル埋め込みになる。(実行時のコストは無い)
- CallerFilePath
- 呼び出し元のファイル名
- CallerLineNumber
- 呼び出し元の行番号
- CallerMemberName
- 呼び出し元のメンバ名(メソッド名、プロパティ名、イベント名等)
public static class Trace
{
public static void WriteLine(string message, [CallerFilePath] string file = "", [CallerLineNumber] int line = 0,
[CallerMemberName] string member = "")
{
var s = string.Format("{0}:{1} - {2}: {3}", file, line, member, message);
Console.WriteLine(s);
}
}
イテレータブロックや非同期関数内から呼び出す場合でも、正常にメソッド名を取得できる。
また、匿名関数(ラムダ式や匿名メソッド式)内で呼び出す場合も、匿名関数を記述しているメソッド名が使用される。
(これらの機能は、内部的に別のクラスやメソッドを生成しているが、そのコンパイラ生成名ではなく、元のメソッド名を取得する)
foreach文の仕様変更
foreach文の仕様が少し変更された。(破壊的変更になるため、既存の構文が動作しない可能性がある)
C# 4.0までは、foreach文は以下のように展開されていた。
これは、ラムダ式を使用してforeach文のループ変数(以下の例では、x)をキャプチャする時、意図しない動作になる。
static void ForeachSample<T>(IEnumerable<T> data)
{
// 展開前
foreach(var x in data)
{
Console.WriteLine(x);
}
// 展開後
using(var e = data.GetEnumerator())
{
T x;
while(e.MoveNext())
{
x = e.Current;
Console.WriteLine(x);
}
}
}
// C# 4.0の場合
var data = new[] {1, 2, 3};
Action a = null;
foreach(var x in data)
{
a += () => Console.WriteLine(x);
}
a();
// 出力
3
3
3
C# 5.0では、foreach文の展開結果において、ループ変数(以下の例では、x)の位置がwhile文の内部に展開するように変更された。
static void ForeachSample<T>(IEnumerable<T> data)
{
// 展開前
foreach(var x in data)
{
Console.WriteLine(x);
}
// 展開後
using(var e = data.GetEnumerator())
{
while(e.MoveNext())
{
T x;
x = e.Current;
Console.WriteLine(x);
}
}
}
これにより、上記のラムダ式の実行結果は、以下のように変更された。
1 2 3
C# 4.0までの仕様が前述のようになっていたのは、以下のような理由によるものである。
- xとdataの登場順序が変わらないように展開するため。
- for文のループ変数と挙動を揃えるため。
C# 5.0で仕様変更した理由は、ラムダ式の利用が広まり、上記のようなループ変数にてキャプチャの問題があったからである。