「Rosly のLanguage Feature Status に並んでいるもののうち、すでに preview 提供済みのものシリーズ第2段。
- field キーワード
- First-class Span ← 今日はこれ
- nameof(T<>)
すでに今、LangVersion にpreview を指定すれば利用可能です。
今日は First-class Span。(これも昔1回取り上げてるんですが、案外書くことあり。)
First-class Span
C# 7.2 の頃にSpan<T> やReadOnlySpan<T> が導入されて以来、これらの型を使った高パフォーマンスな API がたくさん提供されています。また、C# 12 で入ったコレクション式や、C# 13 で入ったparams コレクションでは、T[] やIEnumerable<T> よりもSpan<T> やReadOnlySpan<T> を優先的に選ぶように特別な処理が入っています。この例からもわかるように、今やSpan<T> やReadOnlySpan<T> が重要な地位を占めています。
ところが、コレクション式などの一部の文脈を除いて、Span<T> やReadOnlySpan<T> は「ただの構造体」で、配列からの型変換も「Span<T> やReadOnlySpan<T> 構造体に定義されたユーザー定義型変換」です。C# 言語組み込みの型変換と比べて、ユーザー定義型変換は1段下扱いで、色々な不便があります。
そこで、Span<T> やReadOnlySpan<T> を言語組み込み(= first-class、一級市民)にしたいという提案があって、これもすでに実装があり、Visual Studio 17.13.0 Preview 1 (.NET 9 の正式リリースと同時)で merge 済みです。
わかりやすいのは拡張メソッドの呼び出しで、ユーザー定義型変換を挟む拡張メソッド呼び出しはできません。例えば、以下のコードは C# 13 でコンパイル エラーだったものが、preview ではコンパイルできるようになっています。
// 拡張メソッドの呼び出しはユーザー定義の型変換を見ない。// Span の特別扱いがないと拡張メソッドは呼べない。newint[1].M();staticclassEx{publicstaticvoidM<T>(thisSpan<T>_) { }}
また、「Span<T> やReadOnlySpan<T> 引数を使った方がパフォーマンスがいいのでこちらを呼んでほしい」という要望があるんですが、これまではIEnumerable<T> なオーバーロードがあるとそっちが優先されるという問題もありました。
// ユーザー定義の型変換よりも、「派生・実装クラスだから変換可能」の方が優先度が高い。Ex.M(newint[1]);staticclassEx{publicstaticvoidM<T>(thisIEnumerable<T>_) { }// C# 13 ではこっち。publicstaticvoidM<T>(thisReadOnlySpan<T>_) { }// preview ではこっち。Span の特別扱いがないとこっちは呼んでもらえない。}
また、ユーザー定義の型変換では「型引数の共変性」を表現できないという問題があります。ReadOnlySpan<string> をReadOnlySpan<object> に代入できてもいいはずなのに、これが C# 13 まではできませんでしたが、preview にすると受け付けます。
ReadOnlySpan<string>s= [];ReadOnlySpan<object>span=s;// C# 13 ではエラー。
ちなみに、Span<T> とReadOnlySpan<T> の両方のオーバーロードがある場合、ReadOnlySpan<T> の方が優先されます。
string[]s= [];// ReadOnlySpan の方が優先。s.M();staticclassEx{publicstaticvoidM<T>(thisSpan<T>_) { }publicstaticvoidM<T>(thisReadOnlySpan<T>_) { }}
target-typed で生成される型自体が変わるコレクション式と違って、一度配列を作っちゃってるのでReadOnlySpan<T> を優先しても別にパフォーマンス的なメリットは少ないんですけども。じゃあどうしてこういう仕様にしたかというと…こうしておかないとまた「配列の共変性の地雷を踏むから」とのこと。
string[]s= [];object[]o=s;// C# の配列は共変。// Span を優先するとこれが例外を起こしちゃう。// ReadOnlySpan<object> x = s; は合法。// Span<object> x = s; は実行時例外。o.M();// ReadOnlySpan<object> を優先しないとここで例外が出る。staticclassEx{publicstaticvoidM(thisSpan<object>_) { }publicstaticvoidM(thisReadOnlySpan<object>_) { }}
以上、とりあえず、C# 14 (現状LangVersion preview)ではSpan<T>、ReadOnlySpan<T> が特別扱いされて、オーバーロードの解決順位が変わります。おおむね便利な方向に変わるはずですが、もしかすると何らかの問題を起こす可能性もあります。もしも「Span<T> オーバーロードを呼ばれるとまずい」みたいなことがあれば、OverloadResolutionPriorityとかでの対処を考えてみてください。
