| リリース時期 | 2022/11 |
|---|---|
| 同世代技術 |
|
執筆予定:C# 11.0 トラッキング issue
UTF-8 リテラル
"abc"u8 みたいに、文字列リテラルの後ろに u8 接尾辞を付けることで、UTF-8 な byte 列を文字列リテラルの形で書けるようになりました。
ReadOnlySpan<byte>hex="0123456789ABCDEF"u8;以下のような byte 列とほぼ同じ意味になります。
ReadOnlySpan<byte>s=newbyte[] {97,98,99 };詳しくは「UTF-8 リテラル」で説明します。
生文字列リテラル
C# 11 で、3つ以上の連続した" を使うことで、「一切エスケープが必要ない文字列リテラル」を書けるようになりました。
// """ から始まる文字列リテラル(raw string, 生文字列)。varquote =""" " はそのまま " として使われて、 \ も \ のままの意味。 \\ は \ が2個。 {} とかも特別な解釈はされない。 """;この""" を使った書き方で、さらに文字列補間をすることもできます。
Console.WriteLine(format(123,"abc"));staticstringformat(intid,stringname) =>$$""" { "id":{{id/* ここは補間 */ }}, "name": "{{name/* ここも補間 */}}" } """;詳しくは「生文字列リテラル」で説明します。
required メンバー
プロパティとフィールドに対するrequired 修飾子というものが追加されました。これを使うと、オブジェクト初期化子で何らかの値を代入することを義務付けられます。例えば以下のようなコードを書いたとき、a1 以外のnew A はエラーになります。(警告ではなくエラーにします。)
vara1=newA {X="abc",Y=123 };vara2=newA {X="abc" };// Y を代入していないのでエラー。vara3=newA {Y=123 };// X を代入していないのでエラー。vara4=newA();// X も Y も代入していないのでエラー。classA{publicrequiredstringX {get;init; }publicrequiredintY;}
詳しくは「required メンバー」で説明します。
リスト パターン
C# 11で、[] を使ってリスト(配列やList<T> など)に対するパターン マッチングができるようになりました。例えば以下のようなswitch を書けます。
staticReadOnlySpan<byte>removeBom(ReadOnlySpan<byte>utf8)=>utf8is [0xEF,0xBB,0xBF, ..var noBom]?noBom:utf8;staticboolpalindrome(ReadOnlySpan<int>list)=>listswitch{ []or [_]=>true, [var first, ..var rest,var last]=>first==last&&palindrome(rest),};詳しくは「リスト パターン」で説明します。
Generic Math
インターフェイスの静的メンバーを仮想・抽象にできるようになりました。
この機能の一番の用途は、数値型(int やfloat など)に対するアルゴリズムをジェネリクスを使って書けるようにすることです。この最大用途にちなんで、インターフェイスの静的メンバーなどを含む一連の機能を「generic math」と呼んだりしていました。(コンセプト的な呼び名で、具体的に generic math という名前の文法やライブラリが追加されたわけではありません。)
generic math 関連で、数値型の演算子関連で3つ新機能が追加されています。
インターフェイスの静的抽象メンバー
まず、インターフェイスの静的メンバーについてですが、例えば以下のようなコードが書けるようになりました。
using System.Numerics;// よくある「和を取るコード」なものですら、これまでだとジェネリックに書く手段がなかった。// C# 11 で可能に。staticTSum<T>(IEnumerable<T>items)whereT :INumber<T>{varsum=T.Zero;foreach (varxinitems)sum +=x;returnsum;}// いろんな型に対して sum<T> を呼ぶ。Console.WriteLine(Sum(newbyte[] {1,2,3,4,5 }));Console.WriteLine(Sum(newint[] {1,2,3,4,5 }));Console.WriteLine(Sum(newfloat[] {1,2,3,4,5 }));Console.WriteLine(Sum(newdouble[] {1,2,3,4,5 }));Console.WriteLine(Sum(newdecimal[] {1,2,3,4,5 }));
(詳しくは「インターフェイスの静的抽象メンバー」で説明します。)
符号なし右シフト
符号付き整数(int とかsbyte とか)でも符号なし整数(uint とかbyte とか)でも無関係に、常に「符号なし右シフト(論理シフト)」をするための>>>演算子 (> の数が3つ)が追加されました。
using System.Numerics;sbytes = -1;// ちゃんと符号なし右シフトに。// FF → 7F → 3F → 1F → F → 7 → 3 → 1for (inti = 0;i < 8;i++){Console.WriteLine($"{s:X}");s =LogicalRightShift(s, 1);}// >>> でどの型に対しても符号なし右シフト。staticTLogicalRightShift<T>(Ts,intbits)whereT :IShiftOperators<T,T> =>s>>>bits;詳しくは「【Generic Math】 C# 11 での演算子の新機能」で説明します。
checked 演算子オーバーロード
operator キーワードの後ろにchecked を付けることで、「checked 演算子」を定義できるようになりました。これにより、ユーザー定義の演算子オーバーロードでもchecked(オーバーフロー時に例外を投げる)とunchecked (オーバーフローしても例外を投げない)を切り替えられるようになります。
readonlystructInt2Bit{publicreadonlybyteValue;publicInt2Bit(intvalue)=>Value= (byte)(value&0b11);publicoverridestringToString()=>Value.ToString();publicstaticInt2BitChecked(intvalue)=>valueis<2and>=0?new(value):thrownewOverflowException();publicstaticInt2Bitoperator+(Int2Bitx,Int2Bity)=>new(x.Value+y.Value);publicstaticInt2Bitoperatorchecked+(Int2Bitx,Int2Bity)=>Checked(x.Value+y.Value);}詳しくは「【Generic Math】 C# 11 での演算子の新機能」で説明します。
シフト演算子の右オペランドの制限撤廃
シフト演算子の右オペランドにint 以外の型を使えるようになりました。
structA{// C# 10 以前でも書けるオーバーロード。publicstaticAoperator<<(Ax,inty)=>default;// C# 11 以降でだけ書けるオーバーロード。publicstaticAoperator<<(Ax,Ay)=>default;}詳しくは「【Generic Math】 C# 11 での演算子の新機能」で説明します。
file ローカル型
file という修飾子を使って「書いたファイル内からだけアクセスできる型」を作れるようになりました。
1.M();filestaticclassExtensions{publicstaticvoidM(thisintx)=>Console.WriteLine(x);}
これと同じプロジェクト内の別のファイルに以下のようなコードを書いてもエラーにはなりません。
filestaticclassExtensions{publicstaticvoidM(thisintx)=>Console.WriteLine("別ファイルの file-local Extensions");}
詳しくは「file ローカル型」で説明します。
ref フィールド
ref 構造体のフィールドをref (参照渡し)で持てるようになりました。
ref フィールドの書き方は参照引数や参照戻り値と同じく、型の前にref 修飾を付けます。
refstructByReference<T>{publicrefTValue;}
詳しくは「ref フィールド」で説明します。
その他
ReadOnlySpan に対するパターンマッチ
C# 11 で、ReadOnlySpan<char> に対して文字列リテラルによる定数パターンが使えるようになりました。
// string を渡せたところには ReadOnlySpan<char> を渡せるように。ReadOnlySpan<char>s=Console.ReadLine();// is もif (sis"a") { }// switch ステートメントもswitch (s){case"b":break;}// switch 式も OK。varx=sswitch{"c"=>1,_=>2,};
nameof(引数) のスコープ変更
nameof にちょっとだけ変更が掛かりました。以下のように、メソッドに対する属性の中で、そのメソッドの引数の名前が参照できるようになりました。
using System.Diagnostics.CodeAnalysis;// C# 10 までこの属性、 NotNullIfNotNull("x") と書かないといけなくて割かしつらかった。[return:NotNullIfNotNull(nameof(x))]staticstring?m(string?x)=>x;構造体のフィールドの既定値初期化
C# 11 では、構造体でもフィールドの明示的な初期化が不要になりました。クラスと同じく、明示的に代入しなかったフィールド・自動プロパティには既定値が入ります。
structSample{int_x;int_y;int_z;publicSample(intx,inty) {M();// C# 11 では初期化よりも先に読んでも平気。_x, _y にもこの時点でいったん 0 が入ってる。_x=x;_y=y;// C# 11 では _z に 0 が自動で入る。 }voidM()=>Console.WriteLine($"{_x},{_y},{_z}");}ジェネリックな属性
// 属性クラスをジェネリックにできるように。classTypeConverter<T> :Attribute { }// <> で型引数を指定できる。[TypeConverter<MyConverter>]classMyClass { }文字列補間中の改行
文字列補間で、以下のようなコードが書けるようになりました({} の中で改行を入れれるようになりました)。
vara = 1;varb = 2;vars =$"a:{a// ここで改行できるのは C# 11 から }, b:{b}";ちなみに、以下のように、$@ (文字列補間、かつ、逐語的文字列リテラル)を使う場合には C# 10.0 以前でも以下のようなコードが普通に書けました。
vara = 1;varb = 2;vars =$@"a:{a// $@ の場合は C# 10.0 以前でも OK }, b:{b}";「$"" の場合だけダメだった理由は今となっては思い出せない」というレベルだそうで、仕様漏れ・バグ修正の類にギリギリの「新機能」になります。
Numeric IntPtr
「C# の新機能」と言っていいのかどうか微妙なラインですが、nint に関してちょっとした変更がありました。
C# 9.0 の頃には、IntPtr、UIntPtr 型に算術演算子の定義がなく、nint、nuint に対する演算は C# コンパイラーが特別扱いすることで実装していました。そのため、「nint、nuint は内部的にはIntPtr、UIntPtr としてコンパイルするけども、NativeInteger 属性を付けてnint、nuint かIntPtr、UIntPtr を区別する」みたいなことをしていました。
ところが、 .NET 7 (C# 11 と同世代)では、generic math 導入に伴って、IntPtr、UIntPtr にも算術演算子が導入されました。その結果、C# 9.0 時代のような「特別扱い」が不要になったそうです。そこで C# 11 では、
- .NET 7 移行をターゲットにした場合、
NativeInteger属性を付けない- 正確に言うと、
RuntimeFeatureクラスを見て分岐
- 正確に言うと、
NativeInteger属性がなくてもnint、nuintと同じ扱いをする
みたいな変更が掛かっています。
一応これが既存のコードに対する破壊的変更になる可能性があって、例えば、以下のようなコードはこれまで例外が絶対に出なかったのが、C# 11 以降は例外が出る可能性があります。
unsafevoidM(void*x,inty){varp=checked((IntPtr)x);// unsigned → singed 変換扱いvarz=checked(p +y);}
静的メソッドをデリゲート化するときのキャッシュ化
Numeric IntPtr の話以上に「C# の新機能と言っていいのかどうか微妙」な話(文法的には何も変わっていないし、挙動も大差ない)ですが、Func<int, int> f = Method; みたいな書き方をしたときに、デリゲートのインスタンスをキャッシュするようになりました。
例えば以下のようなコードを考えます。
// この X とintX(int[]data)=>data.Sum(x=>x*x);// この Y、やってることは一緒。intY(int[]data)=>data.Sum(square);staticintsquare(intx)=>x*x;
C# 10 までは、おおむね以下のようなコードに展開されていました。
// ラムダ式だと導入当初からキャッシュが効いてた。Func<int,int>?_anonymous1=null;intX(int[]data){// こんな感じのコードに展開されてて、 new Func<int, int>() のアロケーションは1回限り。_anonymous1??=newFunc<int,int>(x=>x*x);returndata.Sum(_anonymous1);}// ところが、メソッド グループを直接渡した場合、都度 new Func<int, int>() してた(C# 10 まで)。intY(int[]data){// おおむねこういうコードと同じ。varf=newFunc<int,int>(square);returndata.Sum(f);}staticintsquare(intx)=>x*x;
メソッド グループをデリゲート化するとき(Y の側)、常にnew Func<int, int>() のコストがかかっていました。これが、C# 11 からは以下のような感じのコードに展開されます。
// C# 11 で、メソッド グループの場合でも、static なものはキャッシュするようになった。Func<int,int>?_square=null;intY(int[]data){// この類のコードになった。ラムダ式の場合のものと一緒。_square??=newFunc<int,int>(square);returndata.Sum(_square);}staticintsquare(intx)=>x*x;
補足: required, scoped, file キーワードと型名
これまでずっと、C# に新しいキーワードを足したいときには、文脈キーワード(特定の状況下でだけキーワード扱いを受ける)にしてきました。
C# 11 で追加されるrequired,scoped,file の3つも文脈キーワードです。ただ、これらのキーワードは型名と競合しやすい位置に書くことになるので、型名として使えてしまうと文脈からの弁別が難しくなるようで、型名として使えなくしたようです。以下のようにコンパイル エラーになります。
// 古めの文脈キーワードはクラス名にしても警告にしかならない。// 警告の出方も、古いやつは「小文字始まり ASCII のみの型名はやめて欲しい」の CS8981classasync { }classawait { }classdynamic { }// record に関しては専用の警告。CS8860。// 今となっては、これもエラーでよかった説はある。classrecord { }// 最近の文脈キーワードはクラス名にするとエラーにするようにしたみたい。classrequired { }classscoped { }classfile { }// ちなみに、この辺りのクラス名をあえて使いたいときは @ を付けとけば OK。// (警告にもならない。)class@required { }// まあ、@ を付ければ、文脈によらない通常キーワードですら名前に使えるので。class@class { }
マネージ型のポインター
C# 11 から、マネージ型のポインターを使えるようになりました。
unsafe{strings="";Span<byte>x=stackallocbyte[4];// 以下のような型、アドレス取得はこれまではエラーになっていた。// (C# 11 以降も警告にはなる。多少の緩和があった。)string*ps=&s;Span<byte>*px=&x;}
詳しくは「unsafe」で説明します。
