Feature Status: Finishing

C# 7のステータスがいろいろ更新されたみたいですね。

C# 7に入るもの、ほぼ確定したのかなぁ。 C# 7/VB 15には状況が「Finishing(最終作業中)」のものばっかり残して、他は+1(その次)行き。

この感じは、次に出るであろうVS "15" Preview 4が最後のプレビュー版ですかね。

最終作業

最終作業中ってのは、まあ、基本機能は実装済みで、後は実装してみて初めて分かった問題とかバグの修正、みたいな感覚ですかね。 僕が把握してる範囲だと、以下のような作業はしているはず。

まあほんとにこういう細かい作業をする段階という感じです。

見送られた方

じんぐるさんも書いてますけど、元々C# 7/VB 15の欄にいたのに、+1送りになったのもありまして。

まあ、動きがなかったのでうすうす感づいていたものの。以下の3つ。

  • Async Main
  • Source Generation
  • Throw Expressions

Async Main

プログラムのエントリー ポイントとしてTask MainAsync(string[] args)Task<int> MainAsync(string[] args)を認めてほしいという話。

これ、最大の問題は同期コンテキストをどうするかなんですよねぇ。4年近く前にブログに書いたことあるんですけど、GUI (WPFでもUWPでもWinFormsでも)では問題ないのにコンソール アプリで実行すると競合起こす場合がありまして。

これに関する解決案みたいな話が全く見かけなかったんで、今はまだ取り組んでないんだろうなぁと。

ちなみにもう1つの問題として、今、↓みたいな書き方してるコードが結構たくさんあるはずで、これの互換性を崩しかねないからって話もあります。

class Program
{
    static void Main()
    {
        MainAsync().Wait();
    }

    static async Task MainAsync()
    {
        ...
    }
}

こちらはまあ、void Mainの方を優先する、みたいなルールで回避はできるはず。

Source Generation

これは、いろんなところが関係するんで、それが先送りの理由かなぁと。要するに、

  1. C# にコード生成を前提とした新文法を追加する: replace/original キーワードの追加
  2. CodeAnalysis API にSourceGeneratorみたいなクラスを追加して、コンパイラー プラグインとして自作のコード生成処理を挟めるようにする
  3. Visual StudioやVS Codeに、ソースコード書き換え時や、ビルド時にSourceGeneratorを掛ける仕組みを追加する

という3つが必要。

1はあったんですよね。一時期はmasterブランチにもマージされてました。問題もいくつか出てはいたですが:

それよりは、2、3の作業が全然見えてこないなーという感じで。やっぱりC# 7には入れないんだ…

VS "15" Previewのリリース ノートでも、「VS "15"で新たに入る機能はIOperationです」としか書かれてなかったし。 (※IOperationは、C#とVBの両方に対して、単一のプラグインでコード解析・コード修正を掛けれる機能。)

特に今は、Visual Studio、Xamarin Studio、Visual Studio Codeの全部にこの手の仕組みを対応させたがってる節があって、この3者の間で調整してそうな予感が。C#チームの範疇を超えてますし、多少時間掛かりそう。

Throw Expressions

void Method() => throw new NotSupportedException();とか、x ?? throw new ArgumentNullException(nameof(x))とか、int.TryParse(s, out var x) ? x : throw new InvalidOperationException();とか書けるようにしたいって話。

これをやるんだったら、never 型(去年のブログとかBuild Insiderの記事参照)も入れたいとかあるんじゃないかなぁと。

あと、こいつが本格的に必要になるのはmatch式(パターン マッチングのドキュメントのMatch Expressionのところを参照)が入ってからなので、これがC# 7から外れた以上、throw式だけを先に入れる動機付けは弱いはず。

Design Notes 7/15

C# Design Notes for Jul 15, 2016 #12939

で、ちょうど、7/15のDesign Notesが公開されて、ここにOut Varで導入される変数のスコープについて書かれています。

これまで、C#の変数宣言は書ける場所を限定していたのもあって、「宣言しているブロック内がスコープ」というシンプルなルールになっていました。

ところが、C# 7では、

  • Out Var: int.TryParse(s, out var x) ? x : default(int?); みたいに、out引数のところで変数宣言できるようになる
  • Type Switch: obj is int x ? x.ToString() : "unsupported"; みたいに、is演算子で変数宣言できるようになる

という2つの機能が入って、こいつらは、式を掛ける文脈ならどこでも変数宣言できてしまいます。 ここで導入される変数 x のスコープはどの範囲になるべきかという課題がありました。

現状、とりあえず相当厳しい方に倒して実装しています。すなわち、「xは、その式を含むステートメント内でだけ使える」というものです。要するに、以下のコードはコンパイル エラーに。

static void X(string s)
{
    var value = int.TryParse(s, out var x) ? x : 0; // x はこのステートメント内でしか使えない
    Console.WriteLine(x); // もうスコープを外れてる。使えない。コンパイル エラーに
}

いろいろ検討した結果、これはさすがに実際にありそうな用途をカバーしきれないという結論で、制限緩和を考えてるみたいです。

パッと見、「この式を含んでいるブロック内で使える」というのが素直に思えるんですが、if とか for とかが絡むと多少めんどくさく。

例1: if の条件式内で宣言した変数は、else側には伸びてほしくない

if (o is bool b) ...b...; // b はスコープ内
else if (o is byte b) ...b...; // bool の方の b はもうスコープ外。新しいbを作れる
...; // どちらのbもスコープ外

例2: 「含んでいるブロック内」で区切ると、forとかで変になる

for (int i = foo(out int j); ;) ;
// iはスコープ外だけど、jはスコープ内になってしまうのはいい?

例3: ブロックがないifとかどうするの

if (...)
    if (...)
        if (o is int i) ...i...
...; // ブロックを区切りにすると、i はここもスコープになる
     // その手前のifのせいで、iが初期化されている保証できないけどいい?

ってことで、ifとかforとかの場合は「embedded statements内にスコープを限定する」っていうルールにしたいそうです。 embedded statementsって、要するに、ifとかforとかの直後に書けるステートメントのこと。ifとかforとかの後ろには、ブロック、もしくは、何か1つステートメントを書けるわけですが、その部分のことを指すそうです。

余談: embedded statements

この話を見て、embedded statementsって何だっけ?って思って久々にC#の仕様書を眺めてみたんですが…

statement:
    labeled-statement
    declaration-statement
    embedded-statement

embedded-statement:
    block
    empty-statement
    expression-statement
    selection-statement
    iteration-statement
    jump-statement
    try-statement
    checked-statement
    unchecked-statement
    lock-statement
    using-statement 
    yield-statement

つまり、embedded statement = ステートメントのうち、変数宣言とラベル付きステートメント以外。

ということは、こうなる: https://twitter.com/ufcpp/status/761514377543462912

変数宣言はブロックなしでは書けない

まあ、要するに、変数宣言とラベルって、それを囲っているブロックが重要な構文なので、ブロックを省略するとか認めないっていう。言われてみればそうですよねという感じ。

(逆に言うと、そうまでして変数宣言できる位置をちゃんと絞ってたのに、Out VarとType Switchのせいで面倒になったという話。)