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 はやってる
- 8.0 というバージョンから急に null 許容参照型を追加する都合で、
-
リファクタリング
- null かどうかのフロー解析は、リファクタリングの影響を受けるという話
- 例えば、
ifの中身だけを「メソッド抽出」リファクタリングすると、ifの条件式内でやった null チェックの情報を失って(メソッドをまたいだ解析はしないので)、警告が出る
-
!演算子- 場所によって意味変わりすぎ… (前に置くと否定、後ろに置くて「null forgiving」(null チェックを意図的にやらない)アノテーション)
-
ラムダ式中での代入
- ラムダ式でキャプチャした変数までフロー解析するべきかどうか
-
#nullableの影響する範囲はどこまでか- 例えば以下のようなコードを書いた場合、disable になるのは第2型引数の
stringと、Dictionary自体(Dictionaryのシグネチャの一部分である>を範囲に含んでいるので)
- 例えば以下のようなコードを書いた場合、disable になるのは第2型引数の
Dictionary<string,
#nullable disable
string>
# nullable enable
-
null 許容参照型
usingディレクティブ中では認めない? → そのつもりtypeof、nameofは? → 値型の nullable と同じ挙動にしたい。なので、typeofでは認めて、nameofでは認めない
-
(同じく C# 8.0 で入る)
switch式の網羅性(exhaustiveness)とnull- 「非 null 参照型だから null は来ないはず」判定のときに、実際には(フロー解析漏れ/C# 7.X 以前のコード由来のせいで) null が来た時どうするか?
- →
MatchFailureException例外を投げることにする。MatchFailureExceptionの基底はInvalidOperationExceptionにする
-
多重配列問題
string[]?[]とstring[][]?、「null 許容配列の非 null 配列」と「非 null 配列の null 許容配列」どっちがどっち?- → 現状、
string[]?[]の方が「非 null 配列の null 許容配列」 - 多重配列の順序は元々ややこしい…
パターン マッチング
-
x is (a, b)みたいなパターン- x がタプル(
ValueTupe<T>構造体等)なら普通にタプル扱いで分解(要素ごとにマッチング) Deconstructメソッドがあれば(拡張メソッドでの実装を含めて)それを使って分解ITupleインターフェイスを実装していたら、そのインデクサーとかを使って分解- (優先度は上から順。
ITupleインターフェイスを実装していて、かつ、Deconstrcutメソッドも持っていたらDeconstructの方優先)
- x がタプル(
-
0, 1 要素
x is ()認めてくれるらしい(0引数のDeconstruct呼び出し)x is (1)とかも認めてくれるらしい(1引数のDecontruct呼び出し)- これは、パターン マッチング(
isとswitch-case)の時だけ働く。既存の分解代入・分解変数宣言では、キャストとかとの弁別ができないので残念ながら認められない
インターフェイスのデフォルト実装
C# では、base.Member みたいな書き方で、基底クラスのメンバーを呼べます。
(override していても、この書き方なら基底クラス側の実装が呼ばれる。)
で、C# 8.0でインターフェイスのデフォルト実装が入ることで、
「メソッドの実装の多重継承」ができるようになってしまい、
base.Memberだけだと「どっちの基底だよ」と不明瞭に。
そこで、「どの基底インターフェイスのメンバーか」を指定するための構文が必要になります。
候補はたくさん並んでいますが、採用するのは base(BaseInterface).Member みたいな書き方にしたいそうです。
非同期ストリームのキャンセル
非同期ストリーム(IAsyncEnumerable<T>インターフェイス、非同期 foreach、yieldとawaitの混在)に CancellationToken を渡せるようにするという話。
元々「できないとまずいよね」という検討はされてるんですが、やっと「どうするか」まで決めたみたいです。
まず、インターフェイス。GetAsyncEnumeratorの引数にCancellationTokenを足すみたいです。
interface IAsyncEnumerable<T>
{
IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken token = default);
あと、拡張メソッドで IAsyncEnumerable<T> WithCancellationToken<T>(this IAsyncEnumerable<T> e, CancellationToken token) も用意。
非同期 foreach とyieldとawaitの混在については、
細かい単位でCancellationTokenを渡せるような構文を用意するかどうかという検討はしたものの、
とりあえず現状はそういう特別なことはしないという結論に。
