Applibot Advent Calendar 2024 6日目の記事です。
概要
2024/11/13(日本時間) に .NET 9, C# 13, F# 9 が正式リリースされました。
この記事では、今回追加された新機能のうち、個人的に影響度が高いものをピックアップして紹介します。
※ 2024/12/4 現在は公式の日本語記事が不完全なので、英語版を参照しました
.NET 9 ランタイムの新機能
パフォーマンスに影響がありそうな改善のみピックアップします。
- ガベージコレクションの改善
-
パフォーマンス改善
- Exceptionが2〜4倍高速化
- ボックス化オブジェクトのスタック割り当て
- 他、ループ、PGO、Arm 系の最適化
Exceptionが高速化したと言っても、Exceptionを使わないコードと比べれば相当遅いことには変わりないので過信は禁物です。
「ボックス化オブジェクトのスタック割り当て」は詳しく調べる必要がありますが、ボックス化しそうなコードが特定条件下で事実上ボックス化を回避できるようになる機能のようです。
.NET 9 ライブラリの新機能
LINQ 拡張メソッド CountBy AggregateBy の追加
元記事 : LINQ
CountBy はコレクションに含まれる各キーの個数を数える拡張メソッドです。
c.CountBy(x => x.Id) は、 c.GroupBy(x => x.Id).Select(g => new KeyValuePair.Create(g.Key, g.Count())) と同じ結果になります。
Debug.Assertがアサート条件を表示するように改善
元記事 : Debug.Assert reports assert condition by default
Debug.Assert の引数に判定式を渡すと、falseだった場合にデフォルトでその判定式を出力してくれるようになりました。
実装としては、省略可能な第2パラメータ message に CallerArgumentExpressionAttribute をつけ、message なしのオーバーライドの優先度を下げたようです。
Debug.Assert(a > 0 && b > 0);
// false である時の出力
// Process terminated. Assertion failed.
// a > 0 && b > 0
// at Program.SomeMethod(Int32 a, Int32 b)
GetAlternateLookupによるSpanを使ったコレクション検索
元記事 : Collection lookups with spans
高度なパフォーマンスチューニングのための機能です。
Hash<string> や Dictionary<string, T> に対して GetAlternateLookup メソッドを呼び出すことで、 string の代わりに ReadOnlySpan<char> をキーとして使えるラッパー的な構造体が取得できるようになりました。
この機能を使うためには IAlternateEqualityComparer<TAlternate, T> の実装が必要ですが、デフォルトで用意されているのは ReadOnlySpan<char> → string だけのようです。
OrderedDictionary<TKey, TValue>の追加
元記事 : OrderedDictionary<TKey, TValue>
辞書構造であると同時に、追加順が維持されるリストでもあるコレクションです。キー順にソートされるわけではないので注意(キー順でソートされるのは SortedDictionary<TKey, TValue> のほう)。
ReadOnlySet<T>の追加
元記事 : ReadOnlySet<T>
HashSetなどの読み取り専用ラッパーです。
JSONシリアライザーの機能追加
Jsonのシリアライズ・デシリアライズが便利になります。
元記事 : Serialization (System.Text.Json)
参照型のnull許容性を使う
JsonSerializerOptions.RespectNullableAnnotations を true にすることで、デシリアライズする際に参照型のnull許容性が尊重されるようになります。
デシリアライズの過程で null 非許容のプロパティに null を代入しそうになった場合、 JsonException が投げられます。
コンストラクタ・パラメータを必須にする
従来では、コンストラクタベースのデシリアライズ(主にレコード型)では全てのパラメータが省略可能として扱われていました。 JsonSerializerOptions.RespectRequiredConstructorParameters を true にすることで、デフォルト値を指定していないコンストラクタ・パラメータが必須扱いになります。
Enumメンバー名のカスタマイズ
型に JsonConverter 、対象の値に JsonStringEnumMemberName をつけることで、Jsonにシリアライズする際のメンバー名をカスタマイズできるようになります。
using System.Text.Json;
[JsonConverter(typeof(JsonStringEnumConverter))]
enum MyEnum
{
Value1 = 1,
[JsonStringEnumMemberName("Custom enum value")]
Value2 = 2,
}
インデント文字・サイズがカスタマイズ可能に
JsonSerializerOptions.IndentCharacter と JsonSerializerOptions.IndentSize を指定することで、表示に適したJSONが出力できるようになりました。
なお、 IndentCharacter に指定できるのは半角スペースとタブだけのようです。
規定のWeb向けオプションの追加
Web向けのデフォルトオプションとして JsonSerializerOptions.Web が追加されました。以下の定義と同じです。
new JsonSerializerOptions {
// デシリアライズ時に大文字と小文字を区別しない
PropertyNameCaseInsensitive = true,
// シリアライズ時に camelCase を使う
JsonPropertyNamingPolicy = JsonNamingPolicy.CamelCase,
// シリアライズ時、数値は数値として書き込まれる
// デシリアライズ時、文字列から数値への自動変換を許可する(数値からも読み込み可能)
NumberHandling = JsonNumberHandling.AllowReadingFromString,
}
C# 13 の新機能
最近のC#の傾向として、一般開発者ではなくライブラリ開発者に向けた改善が多いです。
params 任意のコレクション
元記事 : params ReadOnlySpan<T> overloads
従来は params をつけられるのは配列だけでしたが、コレクション式で構築できる任意のコレクション型にもつけられるようになりました。基本的には params ReadOnlySpan<T> の形でパフォーマンス改善のために使われることでしょう。
標準ライブラリ内でもこのオーバーロードが追加されたメソッドがあるため、既存コードを .NET9 で再コンパイルするだけでもオーバーロード解決先が変わってパフォーマンス改善が見込めそうです。
partialプロパティ、partialインデクサー
元記事 : More partial members
主に SourceGenerator で使う partial メソッドが、プロパティとインデクサーでも使えるようになりました。自動実装プロパティと見た目が紛らわしい…。
public partial class C
{
// 宣言側
public partial string Name { get; set; }
}
public partial class C
{
private string _name;
// 実装側
public partial string Name
{
get => _name;
set => _name = value;
}
}
Lock オブジェクトの追加
元記事 : New lock object
ロック用に確保している object 型のインスタンスを Lock 型に変えるだけでパフォーマンス改善が見込めます。
using System.Threading;
public class Example
{
// 以前のロックオブジェクトコード
// private readonly object _lockObj = new();
private readonly Lock _lockObj = new();
public void Method()
{
lock (_lockObj)
{
// クリティカルセクション
}
}
}
オーバーロード解決優先度
元記事 : Overload resolution priority
メソッドに OverloadResolutionPriorityAttribute をつけることで、オーバーロードの解決優先度を明示的に制御できるようになりました。
「Debug.Assertがアサート条件を表示するように改善」でさっそく使われています。
イテレータメソッド・ async メソッド内での ref と unsafe
元記事 : ref and unsafe in iterators and async methods
従来は、イテレータメソッドや async メソッドの中では全面的に unsafe ブロックや ref 変数( Span<T> なども含む)を使えませんでしたが、 await や yield をまたがない場合に限って使えるようになりました。
パフォーマンスのために Span<T> を使う機会が多くなってきているのでありがたい変更です。
allows ref struct
元記事 : allows ref struct
従来はジェネリック型に ref 構造体を指定することはできませんでしたが、ジェネリック型制約に allows ref struct をつけることで ref 構造体を型引数に使えるようになりました。
標準のデリゲート Action や Func に allows ref struct 制約が追加されたため、 Span<T> などをやり取りするデリゲートが簡単に定義できるようになりました。
// C#13以降で定義できるようになった
void M(Func<ReadOnlySpan<char>, char> selector)
{
// ...
}
ref struct に対するインターフェイス実装
元記事 : ref struct interfaces
ref 構造体にインターフェイスを実装できるようになりました。ひとつ上の allows ref struct と合わせて、特定の実装を持つ ref 構造体に対するジェネリックなメソッドが作れるようになります。
ただし ref 構造体がボックス化できない制約が消えるわけではないため、インターフェイス型の変数に代入することはできないなど、使用には一定の制限がかかります。
その他
- エスケープシーケンス
\e(U+001B)の追加 (元記事 : New escape sequence) - メソッドグループの自然型 (元記事 : Method group natural type)
- オブジェクト初期化子における末尾からインデックス
^i(元記事 : Implicit index access)
参考文献