昨日なんですけども、2018年に入ってからのC# Language Design Meetingの議事録(design notes)が一斉にアップロードされました。

読むの大変だった… 春分の日でよかった…

一通りなんとなくは目を通したんですけど、ブログ1回の内容じゃなさすぎるので、少しずつネタにしていこうかと。

ここ数時の状況

2週間前にVisual Studio 15.6が正式リリースされて、 その後ほどなくして15.7のプレビュー1もリリースされたわけですけども。

このプレビュー1の時点では 15.7 に C# 7.3 は入っていなかったわけですけども、 roslynリポジトリの15.7マイルストーンを見るとだいぶC# 7.3がらみの作業がマージされている状況です。 作業進捗を表すLanguage Feature Statusのページもつい5日前に更新されて、C# 7.3のところの大半の機能が Merged になりました。

要するに、15.7向けのC# 7.3の最低限の作業が完了したんでしょうね。 あとは正式リリースに向けてバグ出し・バグ修正するフェーズに。 おそらく近々15.7プレビューにC# 7.3対応が来るのではないかと思われます。

ちなみに、roslynのナイトリービルドに挙がっているVSIXやNuGetパッケージをインストールすれば、結構ちゃんとC# 7.3が使えていました

そして、作業が落ち着いたタイミングで毎度やってくる「一斉投稿」が昨日来たと…

最近採用が決まった提案

とりあえず今日はこの話題のみ。 3/19のDesign Noteで今後取り組む作業の選別をしたようで、 いろんな提案issueが新たにChampioned(将来取り組むこと自体は決定)に昇格しています。

取り組み時期はたいてい「8.X」。「8.0ですらなくさらにその後」という意味で、実際のところ「未定」と大差ないやつです。 そんな状態のものなので、具体的な文法はこれからまだだいぶ変わると思います。

default in deconstruction

↓みたいな書き方を認めてほしいというもの。

int x;
int y;
(x, y) = default; // x = default; y = default; と同じ意味

and, or, and not パターン

↓みたいに、パターン マッチングで条件のところに and, or, not を書けるようにしたいとのこと。

switch (o)
{
    case 1 or 2:
    case Point(0, 0) or null:
    case Point(var x, var y) and var p:
    case not string _:
}

型引数の部分的な型推論

いくつか文法案は出ているものの、そのうちの1つで書くと、↓みたいな感じ。

M<int, >(args); // 2個目の型引数だけは args から推論できて、1個目は無理な時、こう書けるようにしたい

制約なしの型引数に対して is null を認めたい

ちょっと説明しにくいんですけど、以下のような感じ。where T : classなしのT tに対して、t is nullを認めたい。

void M(string s)
{
    if (s is null) { } // OK。クラスだし、null チェックしたい
}

void M(int x)
{
    if (x == null) { } // 警告は出るけど別にエラーにはならない。常にfalse
    // ↑あんまり良い話ではないけど、default とかジェネリクスがなかった頃の名残っぽい
}

void M1<T>(T t)
    where T : class
{
    if (t is null) { } // OK。クラス制約あるし。
}

void M2<T>(T t)
{
    if (t is null) { } // 今は NG。
    // とはいえ、構造体の == null が OK なんだから別にこれも認めていいでしょ。常にfalseで
}

暗黙的なスコープのusingステートメント

以下のような、usingしたいリソースがたくさんあるときのネスト問題への対処。

using (var d = SomeDisposable())
{
    // ここのネストが1段深くなるのがしんどい時がある
}

// 特に、多段の時。最後の1個以外は {} を省略できるとはいえ
using (var d1 = SomeDisposable())
using (var d2 = SomeDisposable())
using (var d3 = SomeDisposable())
using (var d4 = SomeDisposable())
using (var d5 = SomeDisposable())
{
}

以下のような書き方を予定。

{
    // 変数宣言の前に using を付けることで、その変数のスコープを using のスコープにする
    using var d1 = SomeDisposable();
    using var d2 = SomeDisposable();
    using var d3 = SomeDisposable();
    using var d4 = SomeDisposable();
    using var d5 = SomeDisposable();

    // Dispose が走るのは、変数がスコープを抜ける時
    // = このブロックから抜けるとき
}

defer ステートメント

Swift にあるやつ。

static void Main()
{
    defer
    {
        Console.WriteLine("関数を抜ける時に呼ばれる"); // 例外があっても常に
    }

    Console.WriteLine("こっちの方が先に表示される");
}

「一回り外側のブロックに影響する」っていう点が気持ち悪くて据え置きになっていたんですが… 前節のusing varを認めてしまった以上、それを理由にリジェクトできなくなった感じ。

前節のusing varを使って、以下のように代用できないこともないんですが、 これだとラムダ式のオーバーヘッド(デリゲートのヒープ確保とインライン展開の阻害)が掛かるのが嫌だそうです。

    using var d = new ActionDisposable(() =>
    {
        Console.WriteLine("関数を抜ける時に呼ばれる");
    });

    Console.WriteLine("こっちの方が先に表示される");

ユーザー定義の位置指定パターン(positional patterns)

パターン マッチング、C# 7.0時点では「型パターン」とか「定数パターン」とか、一部分だけが実装されました。 残りの大部分は、現状、C# 8.0で提供する予定になっています。

そんな中、さらに C# 8.0からも外れて「8.X」にしようという風に外されたのがこいつ。

struct Cartesian
{
    public double X;
    public double Y;
    public Cartesian(double x, double y) => (X, Y) = (x, y);

    // こいつを使った positional パターンは C# 8.0 で入る予定
    public void Deconstruct(out double x, out double y) => (x, y) = (X, Y);
}

class Polar
{
    // こんな感じの定義を書くことで、Cartesian p を p is Polar(var r, var t) みたいなパターンに掛けることができる仕様がある
    // が、こいつは C# 8.0 では入らない
    public static bool operator is(Cartesian p, out double radius, out double theta)
    {
        radius = Math.Sqrt(p.X * p.X + p.Y * p.Y);
        theta = Math.Atan2(p.Y, p.X);
    }
}