10月31日~11月28日当たりの Design Notes 追加。

ちなみにこの辺りの実装、pull request を探してみたら Milestone が 16.0.P2になっているものが結構あって、近々出てくれそうな Visual Studio 16 Preview 1 ではまだ入ってなさそうなもの多めです。

Nullable Reference Types

今回の Design Notes の半分くらいは null 許容参照型がらみ。

あと、書きかけではありますが、やっと null 許容参照型の提案ドキュメントがアップロードされたみたいです。

(これまで、Design Notes に検討事項が散見されていただけで、まとまったページが全くなかったという…)

10月31日のやつは、なんか問題提起だけで結論が入ってなくて、なんかふんわりとした感じ。

  • 明示的な null チェック
    • 8.0 というバージョンから急に null 許容参照型を追加する都合で、string x は「古いコード由来だとnull許容なんだけど、8.0以降+#nullable enableの時には非nullとして扱う」みたいな挙動になる
    • string x に対してif (x == null) があるとき、「明示的に null チェックがあるということは、x 自体は null があり得る」ということなので、string (? が付かない)であっても null 許容扱いする
    • みたいなの、やるべきかどうか。TypeScript はやってる
  • リファクタリング
    • null かどうかのフロー解析は、リファクタリングの影響を受けるという話
    • 例えば、if の中身だけを「メソッド抽出」リファクタリングすると、if の条件式内でやった null チェックの情報を失って(メソッドをまたいだ解析はしないので)、警告が出る
  • ! 演算子
    • 場所によって意味変わりすぎ… (前に置くと否定、後ろに置くて「null forgiving」(null チェックを意図的にやらない)アノテーション)
  • ラムダ式中での代入
    • ラムダ式でキャプチャした変数までフロー解析するべきかどうか

11月5日の:

  • #nullable の影響する範囲はどこまでか
    • 例えば以下のようなコードを書いた場合、disable になるのは第2型引数のstringと、Dictionary 自体(Dictionaryのシグネチャの一部分である>を範囲に含んでいるので)
Dictionary<string,
#nullable disable
    string>
# nullable enable
  • null 許容参照型
    • usingディレクティブ中では認めない? → そのつもり
    • typeofnameofは? → 値型の nullable と同じ挙動にしたい。なので、typeofでは認めて、nameofでは認めない

11月14日の:

  • (同じく C# 8.0 で入る) switch式の網羅性(exhaustiveness)とnull
    • 「非 null 参照型だから null は来ないはず」判定のときに、実際には(フロー解析漏れ/C# 7.X 以前のコード由来のせいで) null が来た時どうするか?
    • MatchFailureException例外を投げることにする。MatchFailureExceptionの基底はInvalidOperationException にする

11月28日の:

  • 多重配列問題
    • string[]?[]string[][]?、「null 許容配列の非 null 配列」と「非 null 配列の null 許容配列」どっちがどっち?
    • → 現状、string[]?[] の方が「非 null 配列の null 許容配列」
    • 多重配列の順序は元々ややこしい…

パターン マッチング

  • x is (a, b) みたいなパターン
    • x がタプル(ValueTupe<T>構造体等)なら普通にタプル扱いで分解(要素ごとにマッチング)
    • Deconstructメソッドがあれば(拡張メソッドでの実装を含めて)それを使って分解
    • ITuple インターフェイスを実装していたら、そのインデクサーとかを使って分解
    • (優先度は上から順。ITupleインターフェイスを実装していて、かつ、Deconstrcut メソッドも持っていたらDeconstructの方優先)
  • 0, 1 要素
    • x is () 認めてくれるらしい(0引数の Deconstruct 呼び出し)
    • x is (1) とかも認めてくれるらしい(1引数のDecontruct呼び出し)
    • これは、パターン マッチング(isswitch-case)の時だけ働く。既存の分解代入・分解変数宣言では、キャストとかとの弁別ができないので残念ながら認められない

インターフェイスのデフォルト実装

C# では、base.Member みたいな書き方で、基底クラスのメンバーを呼べます。 (override していても、この書き方なら基底クラス側の実装が呼ばれる。)

で、C# 8.0でインターフェイスのデフォルト実装が入ることで、 「メソッドの実装の多重継承」ができるようになってしまい、 base.Memberだけだと「どっちの基底だよ」と不明瞭に。

そこで、「どの基底インターフェイスのメンバーか」を指定するための構文が必要になります。 候補はたくさん並んでいますが、採用するのは base(BaseInterface).Member みたいな書き方にしたいそうです。

非同期ストリームのキャンセル

非同期ストリーム(IAsyncEnumerable<T>インターフェイス、非同期 foreachyieldawaitの混在)に CancellationToken を渡せるようにするという話。 元々「できないとまずいよね」という検討はされてるんですが、やっと「どうするか」まで決めたみたいです。

まず、インターフェイス。GetAsyncEnumeratorの引数にCancellationTokenを足すみたいです。

interface IAsyncEnumerable<T>
{
    IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken token = default);

あと、拡張メソッドで IAsyncEnumerable<T> WithCancellationToken<T>(this IAsyncEnumerable<T> e, CancellationToken token) も用意。

非同期 foreachyieldawaitの混在については、 細かい単位でCancellationTokenを渡せるような構文を用意するかどうかという検討はしたものの、 とりあえず現状はそういう特別なことはしないという結論に。