2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

最近の .NET 11 Preview 7 の更新内容は、すでにかなり多い

2
Posted at

.NET 11 は、もはや単純に「API を補う」段階ではなく、言語の表現力、非同期実行の効率、CLI の起動速度、Web の長時間接続におけるリソース占有、デスクトップ UI のモダナイズといった、より低レベルの問題を同時に解決しようとしています。

とくに、いくつかの変更は個別に取り上げる価値があると思います。

C# 15 ではターゲット付きの break / continue をサポートし始め、union と closed 型システムも引き続き整備されています。Runtime では async/await の最適化が階層型コンパイルと JIT にまで深く入り込み、SDK は NativeAOT CLI と MSBuild Server をデフォルトで有効化し始めました。基本ライブラリには IEEE 754 の十進浮動小数点が初めて追加され、ZIP にはパスワードと AES 暗号化も補われています。ASP.NET Core では、Blazor Server の Circuit をバックグラウンドで自動的に「休眠」させる動きも始まっています。Windows Forms でも手を止めておらず、新しいビジュアルスタイル、システムテーマ変更の監視、一括更新時の描画停止が導入されました。

そのためこの記事では、Release Notes を一つずつ翻訳するのではなく、実際に開発体験へ影響すると考える変更を中心に見ていきます。

C# 15: ようやく break に「どの階層から抜けたいか」を伝えられるようになった

まずは、いちばん分かりやすい C# 15 から始めましょう。

少し複雑なループを書いたことがある人なら、たぶん次のようなコードに出会ったことがあるはずです。

for (int row = 0; row < rows; row++)
{
    bool found = false;
    for (int col = 0; col < cols; col++)
    {
        if (matrix[row, col] == target)
        {
            found = true;
            break;
        }
    }
    if (found)
    {
        break;
    }
}

問題は動かないことではなく、実際の意図はとても単純だという点です。見つかったら、外側のループをそのまま終了したいだけです。

しかし以前の C# の break は現在の 1 階層しか抜けられなかったので、状態変数を追加するか、コードをリファクタリングする必要があり、ある人は goto を直接書くことさえありました。

C# 15 では Preview 7 で break と continue に「ターゲット」が追加されました。これでループにラベルを付け、その break がどの階層から抜けるのかを直接指定できます。

コードは次のようになります。

search:
for (int row = 0; row < rows; row++)
{
    for (int col = 0; col < cols; col++)
    {
        if (matrix[row, col] == target)
        {
            Console.WriteLine($"見つかりました: [{row}, {col}]");
            break search;
        }
    }
}

continue も同様です。

たとえば、2 次元データを処理していて、現在の行である条件が成立したら、そのまま次の行へ進みたい場合は次のように書けます。

nextRow:
for (int row = 0; row < rows; row++)
{
    for (int col = 0; col < cols; col++)
    {
        if (ShouldIgnoreRow(row, col))
        {
            continue nextRow;
        }
        Process(matrix[row, col]);
    }
}

これは「見た目は小さいが、実コードでは非常に快適になる」タイプの改善だと思います。

これは従来の意味での goto ではありません。ラベルは for、foreach、while、do、switch のような構造に直接結び付けられており、break / continue も対応する構造の内部でしか使えません。つまり、これは構造化された制御フローのままで、表現力だけが強化されています。

C# のもう一つの重要な方向: union と closed が型システムを補強している

labeled break だけなら、せいぜい糖衣構文の改良に過ぎません。C# 15 でより注目すべきなのは、実は union と closed です。

Preview 7 では union のパターンマッチングがさらに改善されました。今では union に対して pattern matching を行うと、コンパイラは union 自体だけでなく、内部に格納されている実際の値も照合しようとします。

たとえば:

public record Dog(string Name);
public record Cat(int Lives);
public union Pet(Dog, Cat);

Pet pet = new Cat(9);
if (pet is Cat { Lives: > 0 } cat)
{
    Console.WriteLine($"この猫にはまだ {cat.Lives} 匹の命があります");
}

これは、将来的に次のようなモデルが、

Result<TSuccess, TError>
OneOf<A, B>
Message<TextMessage, ImageMessage, FileMessage>

第三者ライブラリに依存したり、自分で継承階層を設計したりしなくてもよくなる可能性を示しています。

同時に、closed 型もより完成度が高まっています。

たとえば:

public closed record Shape;

public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;

基底型が closed として定義されると、コンパイラはこの継承体系が閉じていることを知ります。

その結果、次のような switch では:

static double GetArea<T>(T shape)
    where T : Shape
{
    return shape switch
    {
        Circle(var radius)
            => Math.PI * radius * radius,
        Rectangle(var width, var height)
            => width * height
    };
}

コンパイラはすべての直接派生型がすでに網羅されていることを理解し、ジェネリック型パラメータ T のせいで「ケースが漏れているかもしれない」と誤判断しなくなります。Preview 7 で特に補完されたのは、closed 型に制約されたジェネリックパラメータに対する exhaustiveness analysis、つまり網羅性分析です。

この 2 つの機能を合わせて見ると面白いです。

C# は徐々に、Rust、F#、Swift のような言語でよく見られる能力、つまり「この値が実際には何であり得るのか」をコンパイラにより完全に理解させる方向へ進んでいます。

これは Result、ステートマシン、ドメインモデル、コンパイラ、プロトコル解析のようなコードにとって非常に有用です。

現在のこれらの C# 15 の機能はプレビュー機能なので、試す際はプロジェクト内で明示的に Preview の言語バージョンを有効化できます。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net11.0</TargetFramework>
    <LangVersion>preview</LangVersion>
  </PropertyGroup>
</Project>

公式も closed と System.Text.Json のサンプルで、同じく C# Preview の言語バージョンの使用を明示的に求めています。

Runtime: async/await が本当に低レイヤーへ踏み込んできた

次に注目したいのは、.NET 11 で継続的に追う価値がある Runtime Async の流れです。

以前は次のようなコードを書くと、

async Task<User> GetUserAsync()
{
    return await repository.GetUserAsync();
}

コンパイラは通常、これを非同期ステートマシンへ変換します。

この設計は非常に成熟していますが、ステートマシンということは、生成コードが増え、状態の保存・復元や、それに伴うランタイムコストも発生します。

.NET 11 で進められている Runtime Async は、async/await の実装能力の一部をさらに Runtime 側へ下ろしていこうとしています。

Preview 7 では重要な変更として、Runtime Async 版の非同期メソッドが正式に Tiered Compilation、つまり階層型コンパイルの流れに入ったことが挙げられます。以前はこれらのメソッドは主に高速起動寄りの Tier 0 にとどまっていましたが、ホットな async コードもより積極的に最適化される Tier 1 に入れるようになりました。

ここではかなり極端な最適化例も出ています。

公式のテストでは、完了済み Task を待ち続けるループのシナリオで、JIT が関連する await helper をインライン化できた後、1 億回の呼び出しが約 191 ms から約 32 ms まで下がりました。

別の TechEmpower platform-json テストでは、tail-await が Tier 0 で動作可能になったことで、ウォームアップ中に記録された最大アロケーション率が約 110 MB/s から約 8 MB/s まで低下しました。

もちろん、だからといって Web API を Preview 7 に変えたら直接 6 倍速くなる、という意味ではありません。

JIT は async の意味を理解し始めており、単なる普通のメソッド呼び出しとして扱っていない

たとえば、次のようなコードです。

return Task.CompletedTask;
return Task.FromResult(value);
return ValueTask.CompletedTask;
return ValueTask.FromResult(value);

そして:

new ValueTask(SomeTask())

JIT は今や、よくある Task / ValueTask の factory や adapter をより多く識別でき、適切な場面では不要なラッパーをそのまま削除できます。

この方向が最終的に成熟すれば、ASP.NET Core、データベースクライアント、メッセージシステム、RPC サービスのような、非同期呼び出しに強く依存するプロジェクトへ非常に直接的な影響が出るでしょう。

ついでに言うと、WebAssembly 上の CoreCLR もだんだん実験品らしくなくなってきた

.NET 11 には、見落とされがちなもう一つの流れがあります。それが CoreCLR on WebAssembly です。

Preview 6 の時点で、CoreCLR の WebAssembly 構成はすでに起動できていました。Preview 7 では、.NET Libraries のテストスイートを end-to-end で実行できるようになっています。

その裏には、標準の WebAssembly Exception Handling、RyuJIT の SIMD サポート、ReadyToRun、WASI host、そして WebAssembly 上での診断スタック走査機能が含まれています。

言い換えると、Microsoft は単純に「将来はブラウザ内でも CoreCLR を動かします」と言っているのではありません。

着実に次を埋めています:

 AOT / R2R
 SIMD
 Exception Handling
 WASI
 Diagnostic
 Libraries compatibility

エンジニアリング全体の流れです。

この方向は短期的には普通の業務開発へすぐ影響しないかもしれませんが、Runtime アーキテクチャの観点では非常に注目に値します。

基本ライブラリはかなり強い: .NET に IEEE 754 の十進浮動小数点がついに来た

Libraries の中で「新バージョンらしさ」が最も強い機能を 1 つ選ぶなら、私はこれを挙げます:

Decimal32、Decimal64、Decimal128 です。

これらは System.Numerics にあり、それぞれ 7 桁、16 桁、34 桁の十進精度を提供し、IEEE 754-2019 の decimal floating-point 体系を実装しています。

これは、これまで使ってきた System.Decimal とは別物です。

これらは Infinity や NaN を含む IEEE 浮動小数点の意味論をサポートし、さらに .NET Generic Math にも参加できます。

そのため、今後は次のように書けます。

using System.Numerics;
Decimal64 price = Decimal64.Parse("199.90");
Decimal64 discount = Decimal64.Parse("0.85");
Decimal64 finalPrice = price * discount;
Console.WriteLine(finalPrice);

さらに面白いのはジェネリック数学です。

using System.Numerics;

static T RoundMoney<T>(T value)
    where T : IFloatingPoint<T>
{
    return T.Round(
        value,
        digits: 2,
        MidpointRounding.ToEven);
}

Decimal64 price = Decimal64.Parse("19.995");
Console.WriteLine(RoundMoney(price));

同じジェネリックアルゴリズムで、今後はより多くの数値型をカバーできるようになります。decimal や double ごとに毎回書き直す必要はありません。

.NET 11 では、ジェネリック版の複素数も追加されています:

Complex<float> value = new Complex<float>(3, 4);
Console.WriteLine(value.GetMagnitude());

以前の System.Numerics.Complex は基本的に double に固定されていましたが、新しい Complex では、float、Half、そして新しい Decimal 浮動小数点型などを使えます。型が対応する Generic Math の制約を満たしていれば問題ありません。

こうした変更は、普通の CRUD プロジェクトではまったく体感しないかもしれませんが、科学計算、金融計算、信号処理、数値ライブラリの作者にとってはまったく別の話です。

私が特に気に入った小さな API: TryParsePartial

パーサを書いたことがある人なら、この機能がなぜ便利なのかすぐに分かるはずです。

たとえば、123;456;789 が来たとします。

以前、Span ベースで最初の数値を高性能に解析したいなら、まず区切り文字を自分で探す必要があることが多くありました。

var index = input.IndexOf(';');
var numberSpan = input[..index];
int value = int.Parse(numberSpan);

.NET 11 Preview 7 では、Generic Math に TryParsePartial が追加されました。

これは Span 全体が数値であることを要求せず、どこまで解析できたかを教えてくれます。

たとえば次のように書けます。

using System.Globalization;

ReadOnlySpan<char> input =
    "123;456;789";
if (int.TryParsePartial(
    input,
    NumberStyles.Integer,
    CultureInfo.InvariantCulture,
    out int value,
    out int consumed))
{
    Console.WriteLine(value);
    // 123
    input = input[consumed..];
    Console.WriteLine(input.ToString());
    // ;456;789
}

CSV、プロトコル解析、ログ形式の解析、各種ゼロコピー Parser にとって、この API は非常に扱いやすいです。フィールド境界を見つけるために、文字列を何度も切り出したり、データをコピーしたりする必要がないからです。

HttpClient で Request Body を自然に圧縮できるようになった

HTTP Response の圧縮にはすでに慣れています。

しかし、クライアントが大きな JSON、ログ、またはバルクデータをアップロードする場合、以前は Request Body を圧縮するために自分で Stream を包む必要がありました。

.NET 11 Preview 7 では System.Net.Http に直接、GZipCompressedContentBrotliCompressedContent が追加されました。

さらに ZstandardCompressedContent も追加されています。

たとえば:

using System.IO.Compression;
using System.Net.Http;
using var client = new HttpClient();
var json = new StringContent(
    """
    {
        "name": "張三",
        "message": "Hello .NET 11"
    }
    """);
using var request =
    new HttpRequestMessage(
        HttpMethod.Post,
        "https://example.com/api/messages");
request.Content =
    new ZstandardCompressedContent(
        json,
        CompressionLevel.Optimal);
using HttpResponseMessage response =
    await client.SendAsync(request);
response.EnsureSuccessStatusCode();

ラップすると、Content-Encoding などの情報は対応する Content 型が処理し、内容はストリーミング圧縮されます。

ただし重要な前提があります。Request Compression は現時点では明示的な選択であり、HttpClient がサーバーと自動で「zstd をサポートしていますか」と交渉するわけではありません。

そのため、サーバーが対応するエンコーディング形式を確実にサポートしているか確認する必要があります。

ZIP がついにネイティブでパスワードと AES をサポートした

この変更で、かなりの既存コードが救われるはずです。

System.IO.Compression は、パスワード付き ZIP Entry を直接読み書きできるようになり、次をサポートします:

ZipCrypto
AES-128
AES-192
AES-256

たとえば:

using System.IO.Compression;
const string password = "my-strong-password";
using var archive =
    ZipFile.Open(
        "backup.zip",
        ZipArchiveMode.Create);
var entry =
    archive.CreateEntry(
        "data.txt",
        password: password,
        encryptionMethod: ZipEncryptionMethod.Aes256);
using var stream =
    entry.Open(password);
using var writer =
    new StreamWriter(stream);
writer.WriteLine("この部分の内容はすでに暗号化されています");

読み取りも同様です:

using var archive = ZipFile.OpenRead("backup.zip");
var entry = archive.GetEntry("data.txt")!;
using var stream = entry.Open("my-strong-password");
using var reader = new StreamReader(stream);
Console.WriteLine(reader.ReadToEnd());

レポート出力、データ交換、バックアップツールといったシナリオでは、「ZIP にパスワードを付ける」ためだけに別実装を導入する必要がなくなります。

さらに Preview 7 では、パスワードだけでなく ZIP 作成・展開の Options 型や、より多くの非同期 API も追加されています。

DNS API もついに「このドメインの IP は何か」だけではなくなった

以前、多くの人が .NET の DNS API として知っていたのは、基本的にこれだけでした:

Dns.GetHostAddressesAsync(...)

しかし実際の DNS の世界は、A / AAAA だけではありません。

Preview 7 では、SRV、MX、TXT、CNAME、PTR、NS などのレコードを含む型付き DNS クエリが提供され、さらに DnsResult<T> が返されるようになりました。そこには Response Code や Negative Cache TTL も含まれます。

たとえば:

using System.Net;
DnsResult<SrvRecord> result =
    await Dns.ResolveSrvAsync(
        "_ldap._tcp.example.com");
foreach (SrvRecord record
         in result.Records)
{
    Console.WriteLine(
        $"{record.Target}:{record.Port}");
}

これは Service Discovery、メールシステム、インフラツール、DNS 管理プログラムにとって有用です。

ただし Preview 7 には制限があります。この版の型付き DNS Resolver 実装は現時点ではまず Windows をサポートしており、他のプラットフォームではこの Preview 中に PlatformNotSupportedException が投げられます。

SDK の変化は毎日体感できるかもしれない: dotnet CLI が NativeAOT をデフォルトで使い始めた

続いて開発体験の話です。Preview 7 では、以前は手動で有効化する必要があった機能が、デフォルト動作になりました。NativeAOT dotnet CLI fast path がデフォルトで有効化されたのです。

もちろん、dotnet CLI 全体が突然すべて AOT になったわけではありません。

現在の方針は、NativeAOT 経路で直接処理できるコマンドは、わざわざもう一つの CoreCLR を起動しない、というものです。MSBuild や NuGet のような複雑な機能が必要な場合にのみ、従来の managed CLI にフォールバックします。

効果は非常に分かりやすいです。

Microsoft のテストでは:

dotnet tool list
378 ms → 68 ms

およそ 5.5 倍速くなっています。

dotnet dev-certs httpsdotnet ef のようなツール配布コマンドも、約 700 ms から 200〜220 ms 程度へ短縮されています。

つまり、この最適化が改善しているのは、開発中に非常に煩わしい次のような待ち時間です。

コマンド実行
少し待つ
コマンド実行
また少し待つ

1 回では数百ミリ秒に見えても、1 日に数十回、数百回と実行すると、体感の差ははっきり出ます。

もし一部環境で互換性問題が起きるなら、一時的に無効化することもできます:

DOTNET_CLI_ENABLEAOT=false dotnet --info

MSBuild Server もデフォルトで有効になった

同様の最適化として MSBuild Server もあります。

以前は毎回:

dotnet build
dotnet test
dotnet run

のようなコマンド実行で、MSBuild の起動コストの一部を再度負担することがありました。

現在は SDK がデフォルトで MSBuild Server を warm worker として維持し、つまりすでに温まった MSBuild ワーカープロセスを残すことで、その後の CLI 呼び出しで再初期化を避けます。

こうした最適化はアプリケーションコード自体を変えるものではありませんが、日常の開発体験では最も分かりやすい改善になるかもしれません。

従来の単発モードに戻したい場合は、無効化できます:

export DOTNET_CLI_USE_MSBUILD_SERVER=false

または:

export MSBUILDUSESERVER=0

つまり .NET 11 SDK の考え方は非常に明快です。

繰り返し起動しなくてよいものは、繰り返し起動しない。

NativeAOT CLI は dotnet 自身の起動コストを減らし、MSBuild Server は工程コマンドを繰り返し実行するときの MSBuild 初期化コストを減らします。

dotnet test で「最長どれだけ実行するか」と「何件失敗したら止めるか」を直接指定できるようになった

CI で非常に実用的な機能も追加されています。

Microsoft.Testing.Platform の下では、次のように書けます。

dotnet test --timeout 90s

これは、テスト実行全体に対して最大 90 秒まで許可する、という意味です。

また次も可能です:

dotnet test --maximum-failed-tests 5

これは、失敗が累計 5 件に達したらテスト全体を停止する、という意味です。

一見すると地味ですが、大規模な CI では非常に現実的です。

たとえば 5000 件のテストのうち、最初の 50 件で 20 件連続失敗していたとします。

以前なら残りの 4950 件もそのまま最後まで走り続け、意味がない場合がありました。

今なら直接:

dotnet test \
    --timeout 10m \
    --maximum-failed-tests 20

と書いて、テスト Session 全体に明確な fail-fast 策略を与えられます。

しかもこれらのパラメータを -- の前に置くと、特定のテストアプリケーションだけでなく、Run 全体を制御します。

ASP.NET Core: Blazor Server のバックグラウンドタブがようやく「眠れる」ようになった

ASP.NET Core Preview 7 で最も話題にしたいのは、Blazor です。

Blazor Interactive Server には天然の問題があります。

ブラウザの 1 ページの背後には 1 つの Circuit が対応しています。

ユーザーがページを開いたあと、別のタブに切り替えたり、会議に 1 時間行ったりしても、そのページを誰も見ていなくても、サーバー側の Circuit は資源を使い続けているかもしれません。

Preview 7 では Auto Pause Circuit が導入されました。

基本的な設定は次のような感じです:

<PackageReference
    Include="Microsoft.AspNetCore.Components.Server.AutoPause" />

そして:

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .WithBrowserOptions(options =>
    {
        options.AddAutoPause(pause =>
        {
            pause.Enabled = true;
            pause.HiddenDelay =
                TimeSpan.FromSeconds(30);
        });
    });

ページが非表示になって指定時間に達すると、Circuit は一時停止状態に入り、サーバー資源を解放できます。ユーザーが戻ってきたら再開されます。

ただし Microsoft は雑に「タブが 30 秒隠れたら即停止」とはしていません。

ページ内に未保存のテキスト、ミュートされていない再生中メディア、Picture-in-Picture、Web Lock、あるいは JS Interop、Stream Transfer、Render などのアクティビティがある場合、自動停止は延期されることがあります。

つまり、対象は「本当にアイドルな Circuit」です。

ユーザーが進行中の作業を強制停止するものではありません。

多数の同時接続ユーザーを抱える Blazor Server アプリにとって、この方向はかなり重要だと思います。

Blazor SSR は、コンポーネントツリーの HTML を 1 ブロック分キャッシュできるようになった

もう一つ面白い機能が CacheView です。

以前の SSR では、コンポーネント領域はリクエストごとに毎回インスタンス化され、実行され、レンダリングされていました。

今は次のようにできます:

<CacheView
    ExpiresAfter="TimeSpan.FromMinutes(10)"
    VaryByRoute="productId"
    VaryByQuery="page,pageSize"
    VaryByCulture="true">
    <ExpensiveProductSummary
        ProductId="productId" />
</CacheView>

キャッシュがヒットすると、CacheView 内部の子コンポーネントは再インスタンス化も再レンダリングもされず、以前に捕捉された HTML がそのまま再生されます。

この考え方は、Web 開発ではすでに成熟している次の仕組みにかなり近いです。

Response Cache
Fragment Cache
Partial Cache

ただし今は Blazor のコンポーネントモデルに入っています。

商品詳細ページ、CMS コンテンツ、ナビゲーション領域、コストの高い SSR コンポーネントにとって、この機能は単なるライフサイクル最適化よりもはるかに価値があるかもしれません。

Validation もさらに収束してきた

.NET 11 の最初のいくつかの Preview では、新しい Validation 体系の変更が続いていました。

Preview 7 で重要なのは、ローカライズ機能が直接 Microsoft.Extensions.Validation に入り、個別の Preview Localization パッケージが不要になったことです。

DI に IStringLocalizerFactory が存在すれば、Validation はローカライズの流れに乗れます。

たとえば:

builder.Services.AddLocalization();
builder.Services.AddValidation();
Model:
[ValidatableType]
public class Customer
{
    [Display(Name = "CustomerName")]
    [Required(
        ErrorMessage = "NameRequired")]
    public string? Name { get; set; }
}

ここで CustomerNameNameRequired はリソースキーとしてローカライズに使えます。

この変更は、Validation が MVC の付属機能から、.NET Extensions 全体の基盤へ徐々に変わっていることを示しています。

SSE と OpenAPI もようやく本当にお互いを理解し始めた

現在、AI Streaming、リアルタイムログ、Agent 出力の多くが Server-Sent Events を使っています。

ASP.NET Core 自体はすでに SSE を返せます:

app.MapGet("/messages/stream", (CancellationToken cancellationToken) =>
        TypedResults.ServerSentEvents(
            GetMessagesAsync(
                cancellationToken)));

Preview 7 の変更点は、Endpoint が SseItem<T> を返す場合、OpenAPI 3.2 の文書生成で itemSchema を使って各イベントのデータ型を記述できるようになったことです。単に text/event-stream 全体を文字列として扱うのではありません。

これは SDK Generator、API Explorer、AI API ツールにとって重要です。

「これは text/event-stream です」と、
「これは event stream で、各 event は TodoDto です」は、
情報量がまったく違います。

Windows Forms も寝転んでいない: .NET 11 で新しいビジュアル体系へ移行し始めた

最後に WinForms の話です。

新しい .NET が出るたびに、多くの人はまず「WinForms にも更新があるの?」と思うでしょう。答えは、あるだけでなく、Preview 7 の変更はかなり実用的です。

今の WinForms には新しい VisualStylesMode が追加され、Classic、Disabled、Net11、Latest を明示的に選べます。

たとえば:

using System.Windows.Forms;
Application.SetDefaultVisualStylesMode(
    VisualStylesMode.Net11);
var form = new Form
{
    Text = ".NET 11 WinForms",
    VisualStylesMode = VisualStylesMode.Latest
};
Application.Run(form);

新しいレンダリングモードは、Button、CheckBox、RadioButton、GroupBox、TextBox、RichTextBox などの一般的なコントロールへ順次適用されていきます。TextBox / RichTextBox では、新しいビジュアルスタイルと組み合わせた Padding や角丸表現もサポートされ始めています。

同時に、アプリはシステムのビジュアル設定変更も監視できます。

たとえば Windows の Accent Color の変化です:

Application.SystemVisualSettingsChanged +=
    (_, e) =>
    {
        if ((e.Changed &
            SystemVisualSettingsCategories.AccentColor)
            != 0)
        {
            Console.WriteLine(
                e.NewSettings.AccentColor);
        }
    };

文字スケールの変更通知も受け取れます。

これは、今後 WinForms アプリが Windows のシステムテーマやアクセシビリティの変化に、より自然に反応できることを意味します。

WinForms には、さらに実用的なパフォーマンス API もあります。

もし ListBox、TreeView、ListView に一度に何千件も追加したことがあるなら、画面が激しく再描画され、レイアウト計算が止まらず、最後にはウィンドウがカクカクする、あの感覚を知っているはずです。

以前は、次のものを組み合わせる必要がありました:

BeginUpdate
EndUpdate
SuspendLayout
ResumeLayout

Preview 7 では、この種の要求が、現代的な C# の書き方に非常に合う 1 つの API に統一されました:

using var _ = listBox.SuspendPainting(
        LayoutSuspendTraversal.Target);
for (int i = 0; i < 10_000; i++)
{
    listBox.Items.Add(
        $"Item {i}");
}

using スコープを抜けると、Layout と Painting が復元されます。

もう少し完全に書くと、こうです:

using System.Windows.Forms;
var listBox = new ListBox();
using (
    listBox.SuspendPainting(
        LayoutSuspendTraversal.Target))
{
    for (int i = 0;
         i < 10_000;
         i++)
    {
        listBox.Items.Add(
            $"Item {i}");
    }
}

これは私がとても好きな API 設計です。

機能自体は新しくありませんが、using (...) { } にすることで復元処理が確実に行われるため、手動の Begin / End よりもミスしにくくなっています。

では、.NET 11 Preview 7 は結局何をしているのか

この 6 つの Release Notes を並べて見ると、.NET 11 の方向性はかなり明確だと思います。

今年は 100 個の API を増やす、というタイプではありません。

すでに長年使われてきたコア経路を、下の層から再び最適化しているのです。

C# では、union、closed、網羅性分析によって型システムの表現力を強化しています。

Runtime では、JIT が async/await、Task、ValueTask をさらに深く理解し始めており、コンパイラが生成したステートマシンを単に受け取るだけではなくなっています。

SDK ではさらに直接的で、NativeAOT CLI と MSBuild Server がデフォルトで有効になり、毎日何十回も実行する dotnet コマンドの待ち時間を少しでも減らすことが目的です。

Libraries は長く欠けていた基盤を補っています。IEEE 754 Decimal、ジェネリック Complex、部分的な数値解析、Request Compression、完全な DNS Record クエリ、暗号化 ZIP などです。

ASP.NET Core の焦点の 1 つも、「Blazor をどう動かすか」から徐々に「大規模な Blazor アプリをどう省資源にし、どうキャッシュし、どう診断しやすくするか」へ移っています。

そして WinForms でさえ、新しいビジュアルスタイル、システム設定の変化、一括 UI 更新を真面目に扱い始めています。

もし .NET 11 Preview 7 に 1 つのキーワードを付けるなら、「新機能」ではないと思います。

むしろ、「基盤をさらに下まで作り込むこと」です。

多くのものは、初見ではそれほど驚きません。

break outer?
新しい ZIP API?
MSBuild Server のデフォルト有効化?
バックグラウンドタブで Circuit を停止?

どれも小さなものに見えます。

しかし、あるプラットフォームを 10 年、20 年と使い続けたとき、開発体験に効いてくるのはまさにこうしたものです。

少しだけ構文が楽になる。
CLI の待ち時間が 300 ミリ秒減る。
非同期で 1 回分のアロケーションが減る。
サーバーが誰も見ていない Circuit を何万個も維持しなくて済む。
大量のコントロール追加時に何千回も再描画しなくて済む。

こうした改善は、1 つずつ取り出せば「革命」ではありませんが、積み重なると成熟した開発プラットフォームが進化を続けるやり方そのものになります。

そして Preview 7 の現状を見る限り、.NET 11 はすでにかなり「本気で注目すべき基盤アップグレード」に近づいています。

(Translated by GPT)

元のリンク:https://mp.weixin.qq.com/s/hk9KzwiYKBZbHeRGDeurfQ?token=308772467&lang=zh_CN&wt.mc_id=MVP_325642

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?