目次

キーワード

C# 6

Ver. 6
リリース時期 2015/7
同世代技術
  • Visual Studio 2015
  • .NET Framework 4.6
  • Visual Basic 14
要約・目玉機能
  • C#コンパイラーのC#実装化
  • オープンソース化

C# 5.0」正式リリースの後、「.NET Compiler Platform」の開発が始まり、 当初は既存の C# コンパイラーとの互換性を保つことが優先されていて、しばらく C# の新機能実装が止まっていました。 これまでだと、C# 4.0の正式版がリリースされた瞬間に C# 5.0 のプレビュー版が提供されたりといったように、ほぼ切れ目なく新機能の発表がありましたが、 今回、C# 6 は、 5.0 から2年ほどの空きができました。

しかし、「.NET Compiler Platform」が完成したことで、 かかるコストの割には効果が薄いということでこれまで実装されてこなかったような、ちょっとした便利機能が実装されやすくなりました。 結果として、C# 6 では(C# 5.0 の時の非同期メソッドのような)大きな機能はない代わりに、 (C# 3.0 の時にも似たような)細々とした便利な機能がたくさん追加されそうです。

C# に関わるもう1つの大きな変化としては、C# コンパイラーの開発がオープンになりました(https://github.com/dotnet/roslyn)。 その結果、仕様が固まりきる前の状態が一般の開発者の目に見えるようになりました。

Visual Basicのバージョンは、C# 5.0と同世代のVB 11から一気に14に飛んでいます。 これに関しては、本項の最後に補足。

サンプル

https://github.com/ufcpp/UfcppSample/tree/master/Demo/Csharp6

自動プロパティの拡張

プロパティの自動実装(自動プロパティ、auto-property などと呼びます)自体は C# 3.0 で入った機能です。

C# 2.0 以前 C# 3.0 以降
class Point
{
    private int _x;

    public int X
    {
        get { return _x; }
        set { _x = value; }
    }
  
// Y とか Z も同様に実装 }
class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

しかし、この自動プロパティではいくつか不便な点がありました。それが、C# 6 で改善されています。

初期化子

C# 6 では、自動プロパティに初期化子(プロパティの後ろに = 値; )を与えて、初期値指定ができるようになりました。 後述する getter のみの自動プロパティとの組み合わせが特に便利です。

C# 5.0 以前 C# 6 以降
class Point
{
    // ↓この初期値を設定するためだけに自動実装をやめることに
    private int _x = 10;

    public int X
    {
        get { return _x; }
        set { _x = value; }
    }

    // Y も同様に実装
}
class Point
{
    public int X { get; set; } = 10;
    public int Y { get; set; } = 20;
}
class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point()
    {
        // ↓この初期値を設定するためだけにコンストラクターが必要
        X = 10;
        Y = 20;
    }
}

getter のみの自動プロパティ

初期化子での初期値指定ができるようになったことで、「getter」 のみの自動プロパティが作れるようになりました。

C# 5.0 以前 C# 6 以降
class Point
{
    // ↓getのみの自動実装はできないので仕方なくフィールドを用意
    private readonly int _x = 10;
    public int X { get { return _x; } }

    private readonly int _y = 20;
    public int Y { get { return _y; } }
}
class Point
{
    // ↓ set; を消すだけ
    public int X { get; } = 10;
    public int Y { get; } = 20;
}
class Point
{
    // ↓setをprivateにすることで外からは書き替えれないように
    public int X { get; private set; } 
    public int Y { get; private set; }

    public Point()
    {
        X = 10;
        Y = 20;
    }
}

getだけ書いたプロパティは、readonlyフィールドと同じような扱いになります(というか、実際、コンパイラーによってreadonlyフィールドが生成されます)。 つまり、コンストラクター中でだけ値を設定できて、以降はgetしかできません。

get のみの自動プロパティ 展開結果
public class Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y)
    {
        // コンストラクター内でだけ set 可能。
        // 以降は書き換え不可(readonly)
        X = x;
        Y = y;
    }
}
public class Point
{
    private readonly int _x;
    public int X => _x;

    private readonly int _y;
    public int Y => _y;

    public Point(int x, int y)
    {
        _x = x;
        _y = y;
    }
}

expression-bodied な関数メンバー

Ver. 6

C# 6 では、関数メンバーの関数本体の部分が1つの式だけからなる場合に => を使った簡易文法で関数定義できるようになりました。 { get } や { return } などの記述で間延びしがちな関数メンバー定義が楽になります。 これを、expression-bodied (本体が式の)関数メンバー(expression-bodied function member)と呼びます。

まず、メソッドと演算子オーバーロード(method-like な関数メンバー)では、{ return } を => で置き換えて、以下のように書けます。

C# 5.0 以前 C# 6 以降
public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
    public Point(int x = 0, int y = 0) { X = x; Y = y; }

    public int InnerProduct(Point p)
    {
        return X * p.X + Y * p.Y;
    }
    public static Point operator -(Point p)
    {
        return new Point(-p.X, -p.Y);
    }
}
public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
    public Point(int x = 0, int y = 0) { X = x; Y = y; }

    public int InnerProduct(Point p) => X * p.X + Y * p.Y;
    public static Point operator -(Point p) => new Point(-p.X, -p.Y);
}

また、プロパティとインデクサーの場合は、get-only なものに限って、{ get { return } } を、以下のように置き換えれます。 (一方、get/set 両方持つものに対する省略記法はありません。今まで通りの書き方が必要です。)

C# 5.0 以前 C# 6 以降
public class Polygon
{
    private Point[] _vertexes;

    public int Count
    {
        get
        {
            return _vertexes.Length;
        }
    }
    public Point this[int i]
    {
        get
        {
            return _vertexes[i];
        }
    }
}
public class Polygon
{
    private Point[] _vertexes;

    public int Count => _vertexes.Length;
    public Point this[int i] => _vertexes[i];
}

null 条件演算子

詳しくは「null の使い方」で説明します(予定)が、「引数が有効な値の時だけメソッドやプロパティを参照して、null だったら何も呼ばずに null を返す」というような処理を書きたいことが結構あります。 このような処理を、?. という1つの演算子で簡単に書けるようになりました。 これを null 条件演算子(null conditional operator)といいます。

C# 5.0 以前 C# 6 以降
public class Sample
{
    public string Name { get; set; }

    public static int? X(Sample s)
    {
        if (s == null) return null;
        var name = s.Name;
        if (name == null) return null;
        return name.Length;
    }
}
public class Sample
{
    public string Name { get; set; }

    public static int? X(Sample s) => s?.Name?.Length;
}

インデクサー」に対しても、?[] という形で、null 条件付きの値の取得ができます。

static char? X(string s, int i) => s?[i];

一方で、デリゲートに対して ?() で呼び出しはできません。条件演算子 ? : との区別などで、文法上の問題があるからです。 ただし、この場合でも、 ?.Invoke() という形で null 条件付きの呼び出しができます。

static T Y<T>(Func<T> f)
    where T : class
    => f?.Invoke();

文字列挿入

文字列の整形用の構文が追加されました。

C# 5.0 以前 C# 6 以降
var formatted = string.Format("({0}, {1})", x, y);
var formatted = $"({x}, {y})";

詳しくは「文字列挿入」 で説明します。

nameof 演算子

nameof 演算子(nameof operator)というものが追加され、変数や、クラス、メソッド、プロパティなどの名前(識別子)を文字列リテラルとして取得できるようになりました。

C# 5.0 以前 C# 6 以降
using System;

class MyClass
{
    public int MyProperty { get { return myField; } }
    private int myField = 10;

    public void MyMethod()
    {
        var myLocal = 10;
        Console.WriteLine("MyClass");
        Console.WriteLine("MyProperty = " + MyProperty);
        Console.WriteLine("myField = " + myField);
        Console.WriteLine("MyMethod");
        Console.WriteLine("myLocal = " + myLocal);
    }
}
using System;

class MyClass
{
    public int MyProperty => myField;
    private int myField = 10;

    public void MyMethod()
    {
        var myLocal = 10;
        Console.WriteLine(nameof(MyClass));
        Console.WriteLine(nameof(MyProperty) + " = " + MyProperty);
        Console.WriteLine(nameof(myField) + " = " + myField);
        Console.WriteLine(nameof(MyMethod));
        Console.WriteLine(nameof(myLocal) + " = " + myLocal);
    }
}

詳しくは「nameof 演算子」 で説明しますが、 普通の文字列リテラルと比べた時の nameof 演算子の利点は、ソースコード解析の対象にできることです。

using static

これまで必ず「クラス名.メンバー名」の形で参照する必要があった静的メンバーを、using ディレクティブでクラス指定することで、メンバー名だけで参照できるようになりました。

C# 5.0 以前 C# 6 以降
using System;

class Program
{
    static void Main()
    {
        var pi = 2 * Math.Asin(1);
        Console.WriteLine(Math.PI == pi);
    }
}
using System;
using static System.Math;

class Program
{
    static void Main()
    {
        var pi = 2 * Asin(1);
        Console.WriteLine(PI == pi);
    }
}

Math クラス(System 名前空間)など、純粋な関数のみを持ったクラスに対して特に有効でしょう。

詳しくは「静的メンバー」で説明しています。

インデックス初期化子

オブジェクト初期化子(参考: 「初期化子」 )を書く際に、インデクサーを混ぜれるようになりました。 これをインデックス初期化子(index initializer)といいます。

C# 5.0 以前 C# 6 以降
var dic = new Dictionary<string, int>();
dic["one"] = 1;
dic["two"] = 2;
var dic = new Dictionary<string, int>
{
    ["one"] = 1,
    ["two"] = 2,
};

インデックス初期化子を使う利点は、式しか書けない場面(フィールド初期化子や、expression-bodied な関数定義など)で書けることです。

using System.Collections.Generic;

class Sample
{
    Dictionary<string, int> dic = new Dictionary<string, int>
    {
        ["one"] = 1,
        ["two"] = 2,
    };

    Dictionary<string, int> GetDic(int x, int y) => new Dictionary<string, int>
    {
        ["x"] = x,
        ["y"] = y,
    };
}

プロパティへの代入と、インデクサーへの代入を混在させることもできます。

class Sample
{
    public string Name { get; set; }

    public int this[string key]
    {
        get { return 0; }
        set { }
    }
}

class Program
{
    static void Main()
    {
        var s = new Sample
        {
            Name = "sample",
            ["X"] = 1,
            ["Y"] = 2,
        };
    }
}

例外フィルター

catch 句に追加の条件を付けれるようになりました。

using System;

class Program
{
    static void Main()
    {
        try
        {
            SomeMethod(1, 2);
        }
        catch (ArgumentException e) when (e.ParamName == "x")
        {
            // パラメーター名が x の時だけはエラー無視
        }
        catch (ArgumentException e) when (e.ParamName == "y")
        {
            // パラメーター名が y の時もエラー無視
        }
        catch (ArgumentException e)
        {
            // その他の時
            throw;
        }
    }

    private static void SomeMethod(int x, int y)
    {
        if (x < 0) throw new ArgumentException(nameof(x));
        if (y < 0) throw new ArgumentException(nameof(y));
    }
}

(.NET の実行エンジン的には最初から持っていた機能で、これまでは C# が対応してなかっただけのものです。)

例えば、二種類の例外に対して同じ処理を掛けたい場合や、catchした例外自体ではなくInnerExceptionを見て分岐したい場合などに使えます。

catch/finally 句内での await 演算子

C# 5.0 で導入された await 演算子ですが、5.0では、catch 句と finally 句内には書けないという制限がありました。C# 6 でこの制限がなくなります。

public static async Task XAsync()
{
    try
    {
        await SomeAsyncMethod();
    }
    catch (InvalidOperationException e)
    {
        using (var s = new StreamWriter("error.txt"))
            await s.WriteAsync(e.ToString());
    }
    finally
    {
        using (var s = new StreamWriter("trace.txt"))
            await s.WriteAsync("XAsync done.");
    }
}

catch 句や finally 句での await は、一度全ての例外を拾ってから、非同期処理した後、再 throw するような、結構複雑なコードが生成されます。 C# 5.0 の頃に制限がかかっていた理由は、複雑なコード生成を避けた(最初から難しいことをしてトラブるのを避けた)結果でしょう。

拡張メソッドでコレクション初期化子

C# 3.0 で「コレクション初期化子」が追加されましたが、これは、Add メソッドの呼び出しに展開されるものです。

var x1 = new List<int> { 1, 2, 3 };
// ↑と↓は同じ意味
var x2 = new List<int>();
x2.Add(1);
x2.Add(2);
x2.Add(3);

これまでは、Add は通常のメソッドでないといけませんでした。 これが、C# 6 で、拡張メソッドでもよくなりました。

using System.Collections.Generic;

class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

static class PointExtensions
{
    public static void Add(this List<Point> list, int x, int y)
        => list.Add(new Point { X = x, Y = y });
}

class Program
{
    static void Main()
    {
        var points = new List<Point>
        {
            // PointExtensions.Add が呼ばれる
            { 1, 2 },
            { 4, 6 },
            { 0, 3 },
        };
    }
}

ユーザー定義コード解析に対する #pragma warning

.NET Compiler Platform」 によって誰でもコード解析を追加できるようになりました。 コード解析の追加できるということは、C#に特定用途専用の警告やエラー(と、それに対する修正方法)を追加できるということです。

ところで、C#には、特定の警告を無視するようなプリプロセス命令(参考: 「プラグマ」)があります。 標準の警告には警告番号が付いていて、この番号を指定することで特定の警告を無視します。

#pragma warning で警告を無視する。
#pragma warning で警告を無視する。

.NET Compiler Platform を使うと、誰でもコンパイラー警告を増やせます(ユーザー定義の警告)。 例えば、組織内の標準規約などを決めてある場合、その規約に反するコードには警告を出すということもできます。 これに対して、ユーザー定義の警告を無視できるように、#pragma warning プリプロセス命令も拡張されています。

.NET Compiler Platform で作ったユーザー定義の警告を無視する。
.NET Compiler Platform で作ったユーザー定義の警告を無視する。

更新履歴

ブログ