Visual Studio 16.1 のリリースと、16.2 の Preview 1 が来ていますね。

16.1

16.1 の方は、こないだの Preview 3からそんなに変わってなくて、割かし「リリースされました」という感じ。

C# 8.0 的には、

という感じ。

16.2 Preview 1

IDE 的には以下のようなものが増えてるみたいです。

  • プロジェクトの新規作成で、作成したいアプリのタイプで検索できるように
  • テスト エクスプローラーがだいぶ見やすく

あと、Developer PowerShell (開発ツールがらみの環境変数とかパスが通った状態の PowerShell)が追加されたみたいです。 これまでもあった Developer Command Prompt の PowerShell 版。

ちょっと C# コンパイラーに致命的なバグがありそうなので注意安全な stackallocを使うと不正なコードを生成して、プログラムが起動できなくなります。 (正確に言うと、stackalloc を書いたメソッドを呼んだ瞬間、InvalidProgram例外発生。) 修正済みっぽいんですけど、16.2 Preview 1 には反映されていない状態。

C# 8.0 的には、16.1 の方と以下の差があります。

あと、null許容参照型をプロジェクト単位で有効化するための設定も、<Nullable>enable</Nullable> に変わっています(16.1 までは NullableContextOptions)。

非同期イテレーターの仕様変更

X(ct1).WithCancellation(ct2) みたいなのを書いたときの挙動が変わります。

using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
 
class Program
{
    static async Task Main()
    {
        var c1 = new CancellationTokenSource();
        var c2 = new CancellationTokenSource();
 
        // キャンセルなし
        await foreach (var x in X()) ;
 
        // AscynEnumerable 生成時に c1 が渡る
        await foreach (var x in X(c1.Token)) ;
 
        // GetAsyncEnumerator 時に c2 が渡る
        await foreach (var x in X().WithCancellation(c2.Token)) ;
 
        // 旧挙動: c2 だけが渡る
        // 新挙動: c1, c2 の両方が渡る。内部で CreateLinkedTokenSource
        await foreach (var x in X(c1.Token).WithCancellation(c2.Token)) ;
    }
 
    // 新挙動: EnumeratorCancellation 属性付きの引数は1個に限る
    static async IAsyncEnumerable<int> X([EnumeratorCancellation]CancellationToken ct = default)
    {
        await Task.Yield();
        yield break;
    }
}

base(T) 削除

base(T) アクセス、いったん取りやめになりました。 (書いた記事どうしよう… 消すか、「今後入る予定です」に変えるか…)

C# コンパイラーだけでできる実装方法だと不満だそうで、 .NET Core ランタイム側も合せて修正変更したいそうです。 結果的に C# 8.0 には間に合わず、ランタイム修正ありなものをマイナー リリースするとは思えないので 9.0 以降での実装になります。

stackalloc in nested expressions

式のど真ん中に stackalloc を書けるようになりました。

using System;
using System.Threading.Tasks;
 
class Program
{
    static int M(Span<int> span) => 0;
 
    static void Main()
    {
        // 引数にも書けたり
        M(stackalloc int[1]);
 
        // 式のどこにでも書ける
        if (stackalloc int[1] == stackalloc int[1]) { }
    }
 
    // フィールド初期化子内にも書けたり
    int x = M(stackalloc int[1]);
 
    static async Task Async()
    {
        // 式中に書くなら、非同期メソッド内でも stackalloc が書ける
        M(stackalloc int[1]);
 
        await Task.Yield();
    }
}

ぶっちゃけ、再帰パターンのついでだそうです。 再帰パターンの導入で参照として返せるものの判定が複雑になったらしく、 ちゃんとした判定に書き換えたらついでに stackalloc を書ける場所も増えたとのこと。