Mads (C# コンパイラーのPM)が、去年8月辺りからの C# Language Design Notes がまとめて投稿されました。 たぶん、C# 7の作業が一段落したのかな(最近のC#チームは、実装作業が落ち着くまでドキュメントの類が放置されがち)。

ちなみに内容的には、個別のトピック用のissueページが別にあって、そっちで一通り公開済み。 当然、特に目新しい情報はなくて、まとめと履歴的な状態になっています。

かなりの分量一気に来たのであんまりしっかり読む気にもなれないけども、軽く紹介:

2016/8/24

-C# Language Design Meeting, Aug 24, 2016

  • Task-likeに対して、どうやってmethod builderを取るか決めた
  • フィールド初期化子やコンストラクター初期化子内でのout varを認めたらこんな問題が出るみたいな検討したみたい
    • ※結局、初期化子でのout varの利用自体、今のところは禁止してる

2016/9/6

引数の数が同じDeconstructメソッドがあるときに分解できないの不便だなぁとは思ってたけども。 タプル間の変換を想定してのことらしい。

2016/10/18

C# 7に入れる機能の最終確認をしていたのはこの次期みたい。

  • discardsがC# 7に入るかどうかまだ怪しい時期だったものの、分解とかout varとか、今後追加する文法では_を予約しておこうとはしていたみたい
    • ※ 結局discardsの実装、C# 7に間に合ってる
  • case (int, int) x:みたいなタプルへの型スイッチは認めていない。将来「再帰パターン」って構文が入る予定で、その構文に近くなっちゃうんでその時まで保留
  • ローカル関数では、理想としてはその外でできることは全部ローカル関数内でもできるようにしたかったけど、2点ほど無理なものがあった:
    • コンストラクター内のローカル関数内での readonly field への書き込み
    • 非同期メソッドとイテレーターになっているローカル変数内で、その外の変数の「確実な初期化」保証ができない
      • https://gist.github.com/ufcpp/ad84a38cd8b5e324245bda1472de4679 ←たぶんこういう話
  • _を使った数字区切り、数字の間でだけ認める
    • 0x1a_2bはOKだけど、0x_1a2bはダメ。先頭と末尾を認めない
    • 「区切り」なんだから、数字の間だけであるべき
  • throw式は、???:でだけ使えるように制限した。&&とか||の後ろとか()の中はダメ。
  • タプルの名前の順序間違い、例えばvar (first, last) = (last, first);みたいなの、事故りそうな予感があって警告は出したい
    • ※結局は「工数の問題でC# 7の時点ではできない」「後からWarning Wave追加する」ってなってる(11/15のNotes参照)
  • タプルに対してnew (int x, int y)()は認めないことにしたけども、配列とかnullableはどうかnew (int x, int y)[10]new (int x, int y)?()は認めたい
    • ※結局認めたみたい

2016/10/25

  • C# Language Design Notes for Oct 25 and 26, 2016

  • C# 6の時に一度は検討して、最終的には入れれなかった「変数宣言式」について

    • 宣言式自体は問題あって、提案当時のままというわけにはいかない
    • 宣言式から派生した、分解とout varはC# 7に入った
    • 分解とout varは、再び統一的に扱った方がよさそう(C# 7では間に合わないけど、将来の予定として)
      • 分解代入と分解宣言を混ぜたり: (x, int y) = e
      • out引数のところに分解を書いたり: M(out (x, y))
  • パターン マッチングでは、いくらか、絶対に成功するパターン(irrefutable(反論の余地がない)パターンって呼んでる)がある
    • 今は、絶対に成功していることを前提としたフロー解析(確実な初期化とか、null解析とか)をまだあまりやっていないけど、将来的には役立ちそう
  • 分解のために使うDeconstructメソッド、out引数を使ったもの(Deconstruct(out int x, out int y))じゃなくてタプルを返す("(int x, int y)Deconstruct()")のはどうかというのを検討
    • 計測してみたけども、どっちが効率的かみたいな差は出なかった
    • あんまりやる価値はなさそう
  • if (int.TryParse(s1, out var i)) { ... i ... } みたいな書き方で、Tryの成功時も失敗時も必ずiが初期化されちゃうがために、ifの外でもiが使えてしまうのは問題じゃないか
    • ifの外に変数が漏れる設計にした以上これを防ぐ手立てはないんだけど
    • 「Tryパターンなメソッドのout varの場合はifの外で使うと警告」みたいなことができるAnalyzerは用意した方がいいかも
  • discards に_を使うことにしたけども…
    • 分解とかout varとか、これから足す構文については常にdiscards扱いしたい
    • 既存構文の場合、_を2個以上宣言していたらdiscards扱い、1つも読み出ししてないならdiscards扱い、みたいなルールでやる予定

2016/11/15

  • C# Design Notes for Nov 15, 2016 #16674

  • 要素の名前違いのタプルに関して警告を出すべき状況がある

    • 今は実装していないけど、将来的にはWarning Wavesで警告を追加したい
  • Wildcardsって呼び名をDiscardsに改めたのはこの辺り
    • MVP Summitで、MVPからこの案が出てた
    • *よりも_がいいだろうという感触もここで得た
      • 現状、_が有効な識別子だと言っても、それを「値の無視」以外の目的で使っている人は少なそう

2016/11/30

  • C# Language Design Notes for Nov 30, 2016

  • whileの条件式内のout varで宣言された変数は、whileの外に漏れないようにした

  • forの更新式の中で定義した変数は、forの内側にも漏れないようにした
  • 10/25のNotesで検討していた「分解から宣言式への統一化」、自信をもってできそう
  • 式中で宣言した変数をどこでも使わなかった場合は「未使用変数警告」を出すべきか?
    • そういう変数はたいてい無視するためのダミー変数で、discards機能が入ったら要らなくなるはず
    • でも、警告にまではせずとも、サジェストとして「discardsへの変更」機能を提供することもできる
    • 結局、後者(警告にはしない)を選択
  • C#では、embedded ステートメント(ifとかの後ろ)の中で宣言ステートメントを書くとエラーになるけど、同様の文脈で、今後、分解とかout varとかを使って変数宣言できるようになる。これはエラーにすべきか?
    • しない。むしろ既存の制限の方を緩める可能性すらある(と言ってもC# 7ではやらない)
  • nullでないことを調べるだけのパターンが欲しいって案が出てた
    • やりたい。C# 7までには時間がないけど、アイディアとしてはいい

2016/12/7と12/14

  • C# Design Notes for Dec 7 and Dec 14, 2016 #16709

  • クエリ式中で(out varとかで)宣言された変数は、式中ずっと使えるべきかも

    • where int.TryParse(s, out int i) とかで宣言されたiは、その後ろのselect句とかでも使えるようにする
    • そうなると、単純にWhere(x => int.TryParse(x.s, out int i))みたいな展開はできない。透過識別子が追加される
    • let句での分解(let (dx, dy) = (x - x0, y - y0)みたいなの)でも同様に透過識別子が増える
    • とりあえずC# 7までに実装できる時間はないんで、C# 7時点ではコンパイル エラーにしてある。将来的にはできる余地だけ残してある
  • 10/25で出てた「絶対に成功するパターン(irrefutableパターン)」は、到達可能かどうか(returnthrow、永久ループの後ろに何か書くと警告を出して呉れるやつ)の判定にも使うべきか
    • やってもそこまで価値はなさそうで、既存のセマンティクスを変えるほどのコストは見合わなさそう
  • 11/30で、whileの条件式と、forの更新式中で宣言した変数のスコープを縮めるって話は書いたけど、言い忘れてただけで、do-whileも同様

2017/1/17

  • C# Design Notes for Jan 17, 2017 #16710

  • 定数パターン(e is 42みたいなの)の展開結果をちょっと変更

    • これまでobject.Equals(e, 42)
    • これからobject.Equals(42, e)
    • object.Equalsの中で、第1引数のインスタンス メソッドのEqualsに処理を丸投げしているらしくて、第1引数が定数な(かつ、nullじゃない)保証がある方がちょっとだけパフォーマンスがいいらしい
  • タプルに対する拡張メソッドで、型変換が効くように
    • 配列に対してIEnumerable<T>の拡張メソッドが呼べるんだから、(T[], U[])に対して(IEnumerable<T>, IEnumerable<U>)の拡張メソッドを呼べるべきだろうという提案があった
    • 通常、明示的な型変換が必要な場合に拡張メソッドは呼ばれない。で、ValueTuple間には暗黙的型変換が働かないので、上記拡張メソッドを呼べない
    • でも、全要素がそれぞれ暗黙的に変換できる場合にコンパイラーがタプル間の変換をするんだから、拡張メソッド呼び出しの時にもこの変換を認めるべき
    • これ、「今やる」か「今後もうできないか」の2択なので、もう時間も限られているけど頑張って今やってみる