概要
.NET 11 は2026年11月ごろの正式リリースが予定されています。プレビューも後半戦に入ってきましたので、これまでの Preview で追加された機能をトピックごとに振り返っています。
本記事では .NET ライブラリ(BCL) に関する Preview 5 の変更点をまとめます。System.Text.Json や LINQ、Random など、普段からよく使うクラスへの実用的な追加が多く入っています。数が多いので、特に使いどころの多そうなものを中心にピックアップして紹介します。
-
System.Text.Jsonが JSON Lines のシリアライズに対応 - LINQ に完全外部結合(FullJoin)が追加
-
EqualityComparer<T>にキーセレクターからの生成が追加 -
Randomにジェネリックな数値 API が追加 -
StringBuilderがコピーなしでチャンクを移譲可能に - そのほか(暗号、ネットワーク、オプション検証など)
本記事は .NET 11 Preview 5 の公式リリースノートを元に整理しています。プレビュー段階の内容のため、正式リリースまでに変わる可能性がある点にはご注意ください。
System.Text.Json が JSON Lines のシリアライズに対応
System.Text.Json で、IAsyncEnumerable<T> を JSON Lines 形式でシリアライズできるようになりました。JsonSerializer.SerializeAsyncEnumerable の新しいオーバーロードで topLevelValues: true を指定します。
using System.Text;
using System.Text.Json;
static async IAsyncEnumerable<Reading> GetReadings()
{
yield return new("sensor-1", 21.5);
yield return new("sensor-2", 22.0);
}
await using var stream = new MemoryStream();
await JsonSerializer.SerializeAsyncEnumerable(
stream,
GetReadings(),
topLevelValues: true);
Console.WriteLine(Encoding.UTF8.GetString(stream.ToArray()));
// {"Id":"sensor-1","Value":21.5}
// {"Id":"sensor-2","Value":22}
public sealed record Reading(string Id, double Value);
値の区切りには \n が使われ、WriteIndented は無視されて各項目が1行に収まります。ログストリームやメッセージフィード、バッチ処理などで使いやすい形式です。Stream と PipeWriter の両方に対応しています。
LINQ に完全外部結合(FullJoin)が追加
LINQ に FullJoin 演算子が追加されました。Enumerable / Queryable / AsyncEnumerable のいずれでも使えます。完全外部結合は、マッチしたペアに加えて、両方のシーケンスからマッチしなかった行も返します。SQL で2つのデータセットを突き合わせるときと同じ形をインメモリのクエリでも書けるようになります。
using System.Linq;
Product[] catalog =
[
new("A100", "Keyboard"),
new("B200", "Mouse"),
];
Sale[] sales =
[
new("B200", 4),
new("C300", 2),
];
var rows = catalog.FullJoin(
sales,
product => product.Sku,
sale => sale.Sku,
(product, sale) => new
{
Sku = product?.Sku ?? sale!.Sku,
ProductName = product?.Name ?? "(not in catalog)",
QuantitySold = sale?.Quantity ?? 0,
});
foreach (var row in rows)
{
Console.WriteLine($"{row.Sku}: {row.ProductName}, sold {row.QuantitySold}");
}
public sealed record Product(string Sku, string Name);
public sealed record Sale(string Sku, int Quantity);
あわせて、Join / LeftJoin / RightJoin / GroupJoin にタプルを返すオーバーロードも追加されました。結果セレクターを書かずに IEnumerable<(TOuter, TInner)> を受け取れるので、結合結果がそのままペアになるケースで便利です。
foreach (var (product, sale) in catalog.Join(sales, p => p.Sku, s => s.Sku))
{
Console.WriteLine($"{product.Sku}: {product.Name}, sold {sale.Quantity}");
}
在庫の予定と実績の突き合わせなど、「両方の欠けも見たい」場面は意外と多いので、標準で入るのはありがたいところです。
EqualityComparer にキーセレクターからの生成が追加
EqualityComparer<T>.Create に、キーセレクターを渡すオーバーロードが追加されました。各要素を比較キーに射影して比較する等価比較子を、その場で組み立てられます。型そのものにグローバルな等価セマンティクスを持たせることなく、特定のコレクションやクエリだけ特定のプロパティで比較したい、といったケースに便利です。
using System.Collections.Generic;
var users = new HashSet<User>(
EqualityComparer<User>.Create(
user => user!.Email,
StringComparer.OrdinalIgnoreCase));
users.Add(new User("ada@example.com", "Ada"));
users.Add(new User("ADA@example.com", "Ada Lovelace"));
Console.WriteLine(users.Count); // 1
public sealed record User(string Email, string DisplayName);
これまで IEqualityComparer<T> を実装するクラスをわざわざ用意していたような場面が、1行で書けるようになるのは地味に嬉しい追加です。
Random にジェネリックな数値 API が追加
System.Random に、整数型と2進浮動小数点型に対するジェネリックメソッドが追加されました。NextInteger<T>() 系が整数型を、NextBinaryFloat<T>() が float / double / Half などをカバーします。
Random random = Random.Shared;
int small = random.NextInteger<int>(1, 7); // 1 から 6
long id = random.NextInteger<long>(); // 任意の Int64 値
Half sample = random.NextBinaryFloat<Half>(); // 0.0 <= sample < 1.0
数値型が型パラメーターになっているジェネリックなアルゴリズムから、型ごとに分岐せずに乱数を取得できるようになります。
StringBuilder がコピーなしでチャンクを移譲可能に
StringBuilder.MoveChunks(StringBuilder) で、内部のチャンクチェーンを別の新しいビルダーに O(1) で移譲できるようになりました。移譲元は空になりますが、そのまま再利用できます。
using System.Text;
StringBuilder scratch = new();
scratch.AppendLine("generated line 1");
scratch.AppendLine("generated line 2");
StringBuilder snapshot = StringBuilder.MoveChunks(scratch);
Console.Write(snapshot.ToString());
Console.WriteLine(scratch.Length); // 0
scratch.Append("ready for reuse");
連結済みのテキストを、いったん string に materialize したり各チャンクをコピーしたりせずに受け渡せるため、パーサーやソースジェネレーターなどで役立ちます。所有権の境界としても機能し、エイリアシングによるバグを避けられる点も紹介されています。
そのほかの追加
このほかにも、実用的な API がいくつも追加されています。
-
Process のライン読み取りと KillOnParentExit の拡張:
Process.ReadAllLinesで stdout / stderr を区別しながら同期的に読めるようになり、KillOnParentExitが Linux / Android でも動作するようになった -
Reflection で Nullable の基になる型を公開:
Type.GetNullableUnderlyingType()が追加された -
ネットワーク:
MediaTypeNamesに動画 MIME(Mp4など)の定数が追加、QuicStreamにPriorityプロパティが追加 -
暗号: X25519 の鍵共有を行う
X25519DiffieHellmanが追加(TLS 1.3 や Signal Protocol などで使われる曲線) - Syndication: RSS / Atom フィードの読み込みがトリミング・Native AOT でより安全に
-
オプション検証:
OptionsBuilder<TOptions>.Validate<TValidateOptions>()でバリデーターを型で登録できるようになった
まとめ
-
System.Text.Jsonの JSON Lines 対応や LINQ のFullJoinなど、普段使いのライブラリに実用的な追加が入っている -
EqualityComparer<T>.Createのキーセレクター版やRandomのジェネリック API など、「今までひと手間だった」ものが手軽に書けるようになっている - 暗号(X25519)やネットワーク(QUIC の優先度)まわりなど、モダンなプロトコル対応も進んでいる
数が多いので、気になったものから触ってみると .NET 11 の進化を実感しやすいと思います。
この記事が皆様のコーディングライフの助けになれば幸いです。
参考