目次

Ver. 7.3
リリース時期 2018/5
同世代技術
  • Visual Studio 2017 15.7
  • .NET Core 2.1
要約・目玉機能
  • C# 7.0~7.2のちょっとした改善

C# 7.0 以降の「小数点リリース」も3つ目となりました。 これまでのC# 7系リリースで追加されてきた、 タプル構造体と参照の活用式中での変数宣言になどに関する改善が含まれています。

タプルの ==, != 比較

タプル同士を ==!= 演算子で比較できるようになりました。 以下のように、メンバーごとの==&&で繋いだものに展開されます。

void M((int a, (int x, int y) b) t)
{
    // このタプル == 比較は、
    Console.WriteLine(t == (1, (2, 3)));
    // こんな感じで、メンバーごとの == を && で繋いだものに展開される。
    Console.WriteLine(t.a == 1 && t.b.x == 2 && t.b.y == 3);
}

詳しくは「==、!= での比較」で説明します。

ref 再代入

参照引数、参照ローカル変数に対して、 参照先の値の書き換えではなく、「どこを参照しているか」自体を書き換えることができるようになりました。

int x = 1;
int y = 2;

// x を参照。
ref var r = ref x;

// このとき、r に対する代入は x に反映される。
r = 10; // x が 10 になる。

// これが ref 再代入。
// r が y を参照するようになる。
r = ref y;

// 今度は、r に対する代入が y に反映される。
r = 20; // y が 20 になる。

Console.WriteLine((x, y)); // (10, 20)

また、同時に、forステートメントとforeachステートメントのループ変数を参照ローカル変数にできるようになりました。

詳しくは 「ref再代入」、 「for/foreach のループ変数を参照に」で説明します。

式中での変数宣言(使える場所の拡充)

C# 7.0から式中で、 is 演算子出力変数宣言を使って、 式中でも変数宣言できるようになりましたが、 いくつか制限がありました。 C# 7.3で、これまではできなかった以下の個所でも変数宣言ができるようにありました。

var q =
    from s in new[] { "a", "abc", "112", "132", "451", null }
    where s is string x && x.Length > 1
    where int.TryParse(s, out var x) && (x % 3) == 0
    select s;
using System;

class Derived : base
{
    public Derived(string s) : this(int.TryParse(s, out var x) ? x : -1)
    {
        // コンストラクター初期化子中で宣言した x は、コンストラクター本体内で利用可能。
        Console.WriteLine(x);
    }

    public Derived(int a) : base(out var x)
    {
        // base の場合でも同様。
        Console.WriteLine(x);
    }

    // フィールド初期化子、プロパティ初期化子中で宣言した x は、その初期化子内でのみ有効。
    public int Field = int.TryParse("123", out var x) ? x : -1;
    public int Property{ get; set; } = int.TryParse("123", out var x) ? x : -1;
}

詳しくは「C# 7での新しいスコープ ルール」で説明します。

ジェネリック型引数に対する Enum、Delegate、unmanaged 制約

3つほど指定できる制約が増えました。

型引数に対する制約条件
制約の与え方 説明
where T : unmanaged Tは「アンマネージ型」である
where T : Enum Tは「列挙型」である
where T : Delegate Tは「デリゲート型」である

詳しくは「ジェネリック」、 「unsafe」、 「[余談] 暗黙的な派生」などで説明します。

オーバーロード解決の改善

オーバーロード解決が少し賢くなって、 これまでは呼び分けできなかったようなオーバーロードを呼び分けれるようになりました。

以下のようなものがあります。

  • 静的メソッドかインスタンス メソッドかの違いで解決できるようになった
  • ジェネリック型制約の違いで解決できるようになった
  •   メソッド グループを引数にするとき、メソッドの戻り値を見るようになった

例えば、型制約だと、以下のような拡張メソッドの呼び分けができるようになりました。

using System.Collections.Generic;
using System.Linq;

static class ClassExtensions
{
    // クラスの場合は LINQ の FirstOrDefault そのまま。
    public static T FirstOrNull<T>(this IEnumerable<T> source)
        where T : class
        => source.FirstOrDefault();
}

static class StructExtensions
{
    // 構造体の場合は null 許容型に変える必要がある。
    public static T? FirstOrNull<T>(this IEnumerable<T> source)
        where T : struct
        => source.Select(x => (T?)x).FirstOrDefault();
}

class Program
{
    static void Main()
    {
        // ClassExtensions の方のが呼ばれる。
        new[] { "a", "b", "c" }.FirstOrNull();

        // StructExtensions の方のが呼ばれる。
        new[] { 1, 2, 3 }.FirstOrNull();
    }
}

詳しくは「[雑記]オーバーロード解決」で説明します。

stackalloc 初期化子

stackallocに対して、配列と同じような初期化子を使えるようになりました。 配列同様、初期化子中の要素の型からの推論も効きます。

// 初期化子。{ } を使って初期値を与えられる。
Span<int> x1 = stackalloc int[3] { 0xEF, 0xBB, 0xBF };

// 初期化子があるとき、サイズは省略可能。
Span<int> x2 = stackalloc int[] { 0xEF, 0xBB, 0xBF };

// 初期化子から推論できるときは型名も省略可能。
Span<int> x3 = stackalloc[] { 0xEF, 0xBB, 0xBF };

ユーザー定義型の fixed ステートメント利用

所定のパターンを満たす型に対して fixed ステートメントが使えるようになりました。 以下のように、GetPinnableReferenceという名前のメソッドを用意すれば使えます。

readonly struct Array<T>
{
    private readonly T[] _array;
    public Array(int length) => _array = new T[length];
    public ref T this[int index] => ref _array[index];
    public int Length => _array.Length;

    // このメソッドがあれば fixed ステートメントを使えるようになる
    public ref T GetPinnableReference() => ref _array[0];
}

class Program
{
    static void Main(string[] args)
    {
        var a = new Array<int>(5);

        unsafe
        {
            // fixed (int* p = &a.GetPinnableReference()) に展開される。
            fixed (int* p = a)
            {
                for (int i = 0; i < 5; i++)
                    p[i] = i;
            }
        }

        for (int i = 0; i < 5; i++)
            System.Console.WriteLine(a[i]);
    }
}

詳しくは「ユーザー定義型の fixed ステートメント利用」で説明します。

その他

その他、ほぼ「バグ修正」レベルの改善が2点あります。

自動プロパティのバック フィールドに対する field 属性指定

前者は、自動プロパティに対して field 指定の属性が付けられるようになりました。

using System;

class XAttribute : Attribute { }

class Sample
{
    [field:X] // 自動実装で生成されるフィールドに対する属性の指定
    public int AutoProperty { get; }

詳しくは「プロパティ、イベントと属性の対象」で説明します。

固定長バッファーの読み書きで、fixed ステートメント不要に

固定長バッファーの読み書きをする際、 fixedステートメントが不要になる場面が増えたそうです。

unsafe struct Buffer
{
    public fixed byte A[8];
}

class Program
{
    static Buffer _buffer;

    unsafe static void Main()
    {
        var buffer = new Buffer();
        buffer.A[0] = 1; // 元々 OK
        _buffer.A[0] = 2; // C# 7.3 から OK

        RefFixedBuffer(ref buffer);

        System.Console.WriteLine(buffer.A[0]);  // 元々 OK
        System.Console.WriteLine(_buffer.A[0]); // C# 7.3 から OK
    }

    unsafe static void RefFixedBuffer(ref Buffer buffer)
    {
        buffer.A[1] = 3; // C# 7.3 から OK
    }
}

提案文書にすら、「言語仕様上どうしてこの条件緩和が許されるのかを説明するのが難しい」とか書かれる始末な機能です…

本来はポインター操作になるのでfixedステートメントが必須なんですが、 C# コンパイラー的には参照ローカル変数と同じようなコード生成するらしく、 だったらfixedがなくても平気なはず、と言うことらしいです。

更新履歴

ブログ