Visual Studio 2019 (16.0)が RC までいってちょっと落ち着いたのか、csharplang にちょっと動きが。 (もう、次に C# 8.0 絡みの新機能実装がマージされるとしたら 16.1 になるので、C# チーム的には今ちょっと落ち着ける時期のはず。) Designe Notes 3件追加。
いくつかの話題はすでに個別の issue が立っています。
インターフェイスのデフォルト実装
半分くらいはインターフェイスのデフォルト実装がらみ。
base
呼び出しをどうしようかという話と、アクセシビリティをどうしようかという話。
base
base
呼び出しって言うのは以下のようなやつのこと。
using System;
class A
{
protected virtual void M() => Console.WriteLine("A");
}
class B : A
{
// この、B の M は後から足したり消したり
//protected override void M() => Console.WriteLine("B");
}
// A, B とは別アセンブリにあるとして
class C : B
{
// C から基底クラスの M() を呼ぶ
protected override void M() => base.M();
}
この書き方で、C.M
から基底クラスの M
を呼び出せるわけですが、
-
基底クラスを1つずつたどっていって、最初に見つかったやつが呼ばれる
- コンパイル時に
B.M
があったけど、実行時に読み込んだものからは消えていたらA.M
が呼ばれる - コンパイル時に
B.M
はなかったけど、実行時に読み込んだものには足されていたらB.M
が呼ばれる
- コンパイル時に
みたいな挙動です。 これは C# の仕様というか、 .NET ランタイム(IL 命令)のレベルでそういう仕様だそうです。
で、インターフェイスの場合はダイアモンド継承があり得るので、この仕様便りだと、規定をたどっていく経路が複数あって困る。
なので、base(B).M()
みたいに、具体的にどの型の M
を呼びたいのかを明示できる構文が導入される予定です。これについて、
- この書き方、(C# 8.0 でこれから実装される)インターフェイスのデフォルト実装だけじゃなく、クラスに対しても認める
-
フィールドだろうがなんだろうが、この
base(BaseClass).Member
みたいな書き方が使える- ただし、(overrideさえなければ)元々
this.Member
でアクセスできるものに限る
- ただし、(overrideさえなければ)元々
-
base(B).M()
と書いたらB
だけを見る。実行時に消えて時たら実行時エラーを起こす- 「
B
になければB
から上をたどって探す」みたいなのは現状の .NET ランタイムでは不可能 - 将来的に、 .NET ランタイム自体に改修を入れる余地はある
- 「
とのこと。
アクセシビリティ
アクセシビリティは public
とか private
とかのこと。
C# 7.3 までのインターフェイスは無条件に全部のメンバーが public
(明示的に指定はできない)でしたが、
デフォルト実装とともに、アクセシビリティの指定ができるようになります。
これまでも、public
、protected
、private
は提供するつもりでしたが、
残りの internal
、protected internal
、private protected
も提供することに決めたそうです。
あと、インターフェイスの明示的実装、↓みたいなやつもあるわけですが。
interface I
{
void M();
}
class A : I
{
void I.M() { }
}
デフォルト実装が入ることで、「インターフェイスが基底のメンバーを明示的実装」という状況が発生します。
この場合、その明示的実装は protected
扱いにするそうです。
(前節の「base
は基底をたどって最初に見つかったものを呼ぶ」挙動との兼ね合いだそうです。)
null 許容参照型
値型の default
null 許容“参照型”と言っているわけですから、名前通り、参照型に関する機能です。
でも、じゃあ、クラスとか参照型を含む構造体が絡んだとき、default(T)
はどうするんだという問題が残ります。
(default
既定値は 0/null での初期化になります。nullが絡む。)
なんか今のところ、var y = default(参照型をフィールドとして持つ構造体)
みたいなのに対する警告は出さないみたいです。
(もちろん、「null を認めてないつもりのものに null が混ざる」という落とし穴でしかないので、相当な妥協。)
ただ、C# 8.0 では無理としても、構造体の “defaultability” については今後取り組みたい姿勢はある模様。
3年前(roslyn リポジトリ側に文法に関する提案も混ざってた頃含む)に自分が書いた「default
を認めない値型を作らせてくれ」という issue:
が急に championed (C# チームの誰かが興味を持って取り組む)状態に変わりました。 ある意味こいつは「値型版の null 許容参照型」です。
利用調査
null 許容参照型のフロー解析をオンにしてどうなるか、 それなりの規模なライブラリを調べてみたみたいです(作者に直接聞いたのか、クローンしてきて自分たちでやってみたのか、ブログとかを見ただけなのかとかはわからず)。 Telegram botとかJilとか。 そこで起きてた問題のまとめ。
- メンバー定義で困ることが多い。メソッド内部での問題はむしろ少ない
- インターフェイス側の定義を変えたときの、実装を全部変えて回る作業がやばい
- 初期化子での初期化を前提としているものに対して警告が消せない
- コンストラクター連鎖 (
A() : this(0) { }
みたいなやつ)もフロー解析しきれてない - null 許容値型(既存の、値型に対する
?
) が null 許容参照型との挙動差でよく問題起こす - 診断のクオリティがまだまだ。ジェネリックな型でよく混乱するし、特にタプルに対してつらい
- 自動 code fix 機能欲しい
プロパティの get/set に Obsolete
プロパティの get/set アクセサーには、それぞれ属性が付けれます(メソッド扱い。AttributeTargets.Method
が入ってる属性だけ)。
でも、Obsolete
、Conditional
、CLSComliant
の3つは get/set に付けることは禁止されていました。
で、まあ、Obsolete
だけは認めてもいいんじゃないかという話に。
(あと、Xamarin iOS のやつかな、たぶん、Deprecated
属性も。)
Conditional
、CLSComliant
は今後もノータッチとのこと。
params と文字列補間の効率
提案ドキュメントの背景説明に「MSBuildログ最小限にしてもstringで236MBメモリ食っててその半分がFormatがらみ」とか書かれてて、あっ、はい…そうですよね…
文字列の整形絡みは一時的な小さい文字列インスタンスが大量にできちゃって、結構遅かったりします。
なので、自分も結構仕事で、string.Format
とかを避けて、stackalloc
したりプールした char[]
とかを使って自前で文字列整形することがあったり。
あと、params
が必ず配列を new
しちゃうのも、string.Format
を重たくしている原因。
ということで、corefxlab の方で文字列整形をアロケーションなしでできないか、みたいな実験コードが出ていまして。
C# 側で対応しないといけないこともあるので Design Meeting でも議題に。 概ね、
params
にSpan<T>
を認めて、スタックに値を置いて可変長引数呼び出ししたい- 値型のボックス化避けたいから
Variant
型作るか
みたいな話。
まあでも、付いたコメント的には
- なんで
stackalloc
に参照型使えないの? → それを認めようとするとガベコレのコードがだいぶ複雑になる(パフォーマンスにも悪影響) Span<TypedReference>
を認められるようにしようよ
みたいな感じ。
switch 式の優先度
今回の Designe Notes にはないんですけど、もう1個、割と最近立った提案 issue:
現時点 (Visual Studio 2019 RC でのことなので、たぶん、RC が外れても)での switch
式の結合優先度は関係演算子(==
とか)と同じだそうです。
関係演算子って結構優先度が低くいですし、
&
よりは上で +
よりは下みたいな位置です。
x switch { ... } + 1
みたいなものが、} + 1
の方を先に見ちゃってエラーにa + b switch {...}
は+
が先だけど、a & b switch {...}
はswitch
が先
とかいう嫌な状態。
なので、プライマリな優先度(x.M()
の .
とか、[]
とかと同じ)に変えようかという提案。
大体、x switch { ... }
が後置き演算子みたいな見た目ですからね。
[]
とか ++
とか .
とか、後ろに置くものは大体がプライマリ。
それに合わせた方が自然だろうという意図もあり。
Index/Range の実装再考
もう1つは C# 側じゃなくて、corefx 側から出ている要望。
今現在の仕様だと、C# の姿勢としては、
^a
とか とかa..b
とかの構文はそれぞれIndex
/Range
構造体を作るだけx[a..b]
みたいなのを使いたければ、コレクション クラスの実装側にIndex
/Range
を受け付けるオーバーロードを増やせ
です。
これに対して、要するに、
- インデクサーを持つコレクション クラス全部に対してオーバーロードを増やして回るのは大変だし、パフォーマンスが出ない実装になることがある
- それよりは、
collection[i.GetOffset(collection.Count)]
みたいな、int
のオーバーロードを使うコードに展開する実装に変えてほしい
という流れ。 まあ、そりゃ、コレクション作ってる側からするとそうですよね。 corefx (中の人同士)ですらそうなんだから、ましてコミュニティ実装なコレクションだとなおのこと。