目次

C# 7.1

Ver. 7.1
リリース時期 2017/8
同世代技術
  • Visual Studio 2017 Update 3 (15.3)
要約・目玉機能
  • C# 7.0のちょっとした改善

2017年8月、すなわち、C# 7.0のリリース(2017年2月)から半年足らずで C# 7.1 がリリースされました。

C# 7.0の頃から、目標としては C# のリリース サイクルの短縮を考えていました。 多くの機能を2・3年に1度一気にリリースするよりも、細かく出せるものに関しては短いリリース サイクルで出したいという意図です。 今回、(実質的に)初の「マイナー バージョンアップ」となる C# 7.1 が誕生しました。

( 一応、C# 1.1があったんですが、ほとんど使われない機能が2つ追加されただけなので、1.1があったこと自体あまり認知されていないものです。)

C# 7.1 は、Visual Studio 2017のリリース時期に間に合わなかった C# 7.0 の積み残しと言った感じの、小さい機能が4つほど追加されています。

非同期Main

Mainメソッドの戻り値にTaskクラス(System.Threading.Tasks名前空間)を使えるようになりました。 以下のいずれかのオーバーロードであればエントリーポイントとして認識されます。

static Task<int> Main()
static Task<int> Main(string[] args)
static Task Main()
static Task Main(string[] args)

詳しくは、「非同期Main」で説明します。

default 式

これまでも既定値を作るために、default(T)という構文がありましたが、 型名Tの指定が煩雑でした。 特に、名前の長い型に対してdefault(T)を使うと、かなりのうっとおしさがあります。

既定値を結構使って、かつ、名前が長い型というと、例えばCancellationToken構造体(System.Threading名前空間)とかです。 以下のようなコードを書いたりします。

static async Task DefaultExpression(CancellationToken c = default(CancellationToken))
{
    while (c != default(CancellationToken) && !c.IsCancellationRequested)
    {
        await Task.Delay(1000);
        Console.WriteLine(".");
    }
}

これに対して、C# 7.1では、左辺(代入先)から推論できる場合に、(T)を省略してdefaultだけで既定値を作れるようになりました。 例えば先ほどのコードは以下のように書き直せます。

static async Task DefaultExpression(CancellationToken c = default)
{
    while (c != default && !c.IsCancellationRequested)
    {
        await Task.Delay(1000);
        Console.WriteLine(".");
    }
}

既定値自体や、default(T)の説明は「既定値」を参照してください。

タプル要素名の推論

タプルの要素名が、タプル構築時に渡した変数から推論できるようになりました。 例えば以下のように、(x, y) と書くだけで、1要素目にx、2要素目に y という名前が付きます。 (これまでだと、(x: x, y: y) と書く必要があった。)

var x = 1;
var y = 2;
var t = (x, y);

// C# 7.0。t の要素には名前が付かない
Console.WriteLine(t.Item1);
Console.WriteLine(t.Item2);

// C# 7.1。(x, y) で (x: x, y: y) 扱い
// t の要素に x, y という名前が付く
Console.WriteLine(t.x);
Console.WriteLine(t.y);

詳しくは「タプル」で説明します。

ジェネリック型に対するパターン マッチング(型スイッチ)

C# 7.0でisswitchで型を見ての分岐ができるようになりました。 しかし、ジェネリクスが絡む場合、 例えば以下のようなコードはC# 7.0ではコンパイル エラーになっていました。

static void M<T>(T x)
{
    switch (x)
    {
        case int i:
            break;
        case string s:
            break;
    }
}

Tintstringとして処理できない」と言った旨のコンパイル エラーが出ます。

さらにいうと、以下のような需要が結構ありそうな場面でも、C# 7.0ではコンパイル エラーになりました。

class Base { }
class Derived1 : Base { }
class Derived2 : Base { }
class Derived3 : Base { }

// こういう、型制約付きのやつですら 7.0 ではダメだった
static void N<T>(T x)
    where T : Base
{
    switch (x)
    {
        case Derived1 d:
            break;
        case Derived2 d:
            break;
        case Derived3 d:
            break;
    }
}

C# 7.0でも、以下のように、as演算子を使った場合にはちゃんとコンパイルできます。 型スイッチは、内部的にはas演算子に展開される機能で、as演算子にできて型スイッチにできないことがあるのは不自然です。

static void N<T>(T x)
    where T : Base
{
    { var d = x as Derived1; if (d != null) { return; } }
    { var d = x as Derived2; if (d != null) { return; } }
    { var d = x as Derived3; if (d != null) { return; } }
}

そこで、C# 7.1では、上記コードのような、ジェネリックな型に対する型スイッチを使えるようになりました。 (新機能というよりは、仕様漏れ・バグ修正の類です。)

パターンマッチング(型スイッチ)自体の説明に関しては「型スイッチ」を参照してください。

更新履歴

ブログ