バインドされていないジェネリック型のnameof(Unbound generic types and nameof)
C# 14では、nameof演算子がバインドされていないジェネリック型に対しても、名前を返すようになりました。
nameof演算子は、変数名、型、メンバー、メソッド名などの識別子を与えると、それを表す文字列を返します。識別子に相当する文字列を得たり、識別子名によって処理を分けたい場合などに使用します。
Console.WriteLine(nameof(List<int>)); // List
Console.WriteLine(nameof(List<int>.Count)); // Count
var numbers = new List<int> { 1, 2, 3 };
Console.WriteLine(nameof(numbers)); // numbers
Console.WriteLine(nameof(numbers.Add)); // Add
従来、nameof演算子の制約として、バインドされていないジェネリック型(非結合ジェネリック型)には使えないというルールがありました。バインドされていないとは、T<>のように山カッコ内が空で、型パラメーターが指定されていない状態を言います。
C# 14では、以下のリストのように非結合ジェネリック型であるList<>についても文字列を返すようになりました。
Console.WriteLine(nameof(List<>)); // List
細かな変化ですが、nameof演算子を利用できる局面が広がったと言えるでしょう。
第1級に昇格したSpan<T>およびReadOnlySpan<T>(Implicit span conversions)
C# 14では、Span<T>およびReadOnlySpan<T>が言語構文的に特別扱いされるようになり、これらの型を優先した変換が行われるようになりました。
Span<T>およびReadOnlySpan<T>は、第2回で紹介したref構造体の一つです。
ref構造体とは、その置き場所がスタックに限定されるというものでした。Span<T>およびReadOnlySpan<T>自体は、メモリ領域の場所と大きさを持つだけの単純な構造ですが、スタックに置かれるという性質から連続したメモリ領域を取り扱うのに効率的で、そのために標準ライブラリをはじめSpan<T>およびReadOnlySpan<T>を使う局面は増加する一方となりました。
これに伴い、標準ライブラリなどではSpan<T>およびReadOnlySpan<T>を使うオーバーロード、従来のIEnumerable<T>を使うオーバーロードというように混在することになり、配列であるT[]についてどちらを呼べばいいか決められない、といった問題がありました。
例えば以下のようなケースでは、オーバーロードの優先順位を決定できずに、コンパイルエラーが発生します。
int[] data = {1, 2, 3};
Class.Method(data);
class Class
{
public static void Method(IEnumerable<int> values) { }
public static void Method(ReadOnlySpan<int> values) { }
}
↓
error CS0121: 次のメソッドまたはプロパティ間で呼び出しが不適切です: 'Class.Method(IEnumerable<int>)' と 'Class.Method(ReadOnlySpan<int>)'
C# 14では、Span<T>およびReadOnlySpan<T>がintやdoubleといった組み込み型(これを「第1級オブジェクト」と呼んだりします)と同じ扱いになり、優先して変換されるようになりました。これにより、Span<T>およびReadOnlySpan<T>を使うオーバーロードが常に優先して呼び出されるようになり、上記のコードはエラーとはなりません。
まとめ
今回は、拡張メンバーのためのextensionブロック、左辺値での使用が可能になったnull条件付き演算子、バインドされていないジェネリック型で使えるようになったnameof演算子、第1級に昇格したSpan<T>およびReadOnlySpan<T>を紹介しました。
次回は、ラムダ引数に指定できるようになった修飾子、バッキングフィールドなしでプロパティを記述できるfieldキーワード、部分メンバーとしてのインスタンスコンストラクターとイベント、ユーザー定義可能な複合代入演算子などを紹介します。
