次の方法で共有


.NET 8 ランタイムの新機能

この記事では、.NET 8 用 .NET ランタイムの新機能について説明します。

パフォーマンスの向上

.NET 8 には、コード生成と Just-In-Time (JIT) コンパイルの機能強化が含まれています。

  • Arm64 のパフォーマンスの向上
  • SIMD の機能強化
  • AVX-512 ISA 拡張機能のサポート (Vector512 および AVX-512を参照)
  • クラウドネイティブの機能強化
  • JIT スループットの向上
  • ループと一般的な最適化
  • ThreadStaticAttribute でマークされたフィールドの最適化されたアクセス
  • 連続レジスタの割り当て。 Arm64 には、テーブル ベクター参照の 2 つの命令があり、タプル オペランド内のすべてのエンティティが連続するレジスタに存在する必要があります。
  • JIT/NativeAOT では、コンパイル時にサイズを決定できる場合に、比較、コピー、ゼロなど、SIMD を使用して一部のメモリ操作をアンロールおよび自動ベクター化できるようになりました。

さらに、動的プロファイルガイド付き最適化 (PGO) が改善され、既定で有効になりました。 有効にするために、ランタイム構成オプション 使用する必要がなくなりました。 動的 PGO は階層化コンパイルと連携して動作し、階層 0 の間に配置される追加のインストルメンテーションに基づいてコードをさらに最適化します。

動的な PGO では、平均してパフォーマンスが約 15% 向上します。 約 4,600 個のテストのベンチマーク スイートでは、23%、20% 以上のパフォーマンスが向上しました。

Codegen 構造体の昇格

.NET 8 には、構造体変数を昇格させる JIT の機能を一般化する codegen 用の新しい物理昇格最適化パスが含まれています。 この最適化 (集計 のスカラー置換とも呼ばれます) は、構造体変数のフィールドを、JIT が推論し、より正確に最適化できるプリミティブ変数に置き換えます。

JIT では既にこの最適化がサポートされていますが、次のようないくつかの大きな制限があります。

  • これは、4 つ以下のフィールドを持つ構造体でのみサポートされていました。
  • 各フィールドがプリミティブ型の場合、またはプリミティブ型をラップする単純な構造体である場合にのみサポートされていました。

物理的な昇格により、これらの制限が削除され、長年の JIT の問題が多数修正されます。

ごみ回収

.NET 8 では、その場でメモリ制限を調整する機能が追加されています。 これは、需要が出てくるクラウド サービス シナリオで役立ちます。 コスト効率を高めるためには、需要の変動に応じて、サービスのリソース使用量をスケールアップおよびスケールダウンする必要があります。 サービスが需要の減少を検出すると、メモリ制限を減らすことでリソースの消費量をスケールダウンできます。 以前は、ガベージ コレクター (GC) が変更を認識できず、新しい制限よりも多くのメモリを割り当てる可能性があるため、これは失敗していました。 この変更により、RefreshMemoryLimit() API を呼び出して、新しいメモリ制限で GC を更新できます。

注意すべきいくつかの制限事項があります。

  • 32 ビット プラットフォーム (Windows x86 や Linux ARM など) では、まだヒープ のハード制限がない場合、.NET は新しいヒープ ハード制限を確立できません。
  • API は、更新に失敗したことを示す 0 以外の状態コードを返す場合があります。 スケールダウンが過剰で、GC が動く余地がなくなると、このようなことが発生する可能性があります。 この場合は、GC.Collect(2, GCCollectionMode.Aggressive) を呼び出して現在のメモリ使用量を縮小してから、もう一度やり直してください。
  • GC が起動時にプロセスで処理できると思われるサイズを超えてメモリ制限をスケールアップすると、RefreshMemoryLimit 呼び出しは成功しますが、制限として認識されるメモリよりも多くのメモリを使用することはできません。

次のコード スニペットは、API を呼び出す方法を示しています。

GC.RefreshMemoryLimit();

メモリ制限に関連する GC 構成設定の一部を更新することもできます。 次のコード スニペットでは、ヒープのハード制限を 100 メビバイト (MiB) に設定します。

AppContext.SetData("GCHeapHardLimit", (ulong)100 * 1_024 * 1_024);
GC.RefreshMemoryLimit();

たとえば、ヒープのハード制限の割合が負の場合やハード制限が低すぎる場合など、ハード制限が無効な場合、API は InvalidOperationException をスローできます。 これは、新しい AppData 設定が原因か、コンテナー メモリ制限の変更によって暗黙的に更新が設定されるヒープ ハード制限が、既にコミットされているものより低い場合に発生する可能性があります。

モバイル アプリのグローバリゼーション

iOS、tvOS、MacCatalyst のモバイル アプリでは、軽量の ICU バンドルを使用する新しい ハイブリッド グローバリゼーション モードを選択できます。 ハイブリッド モードでは、グローバリゼーション データは ICU バンドルから部分的にプルされ、一部はネイティブ API への呼び出しから取得されます。 ハイブリッド モードは、モバイル でサポートされているすべてのロケールを提供します。

ハイブリッド モードは、インバリアント グローバリゼーション モードでは機能せず、モバイル上の ICU データからトリミングされたカルチャを使用するアプリに最適です。 小さい ICU データ ファイルを読み込む場合にも使用できます。 (icudt_hybrid.dat ファイルは、既定の ICU データ ファイル icudt.datよりも 34.5 % 小さいです)。

ハイブリッド グローバリゼーション モードを使用するには、HybridGlobalization MSBuild プロパティを true に設定します。

<PropertyGroup>
  <HybridGlobalization>true</HybridGlobalization>
</PropertyGroup>

注意すべきいくつかの制限事項があります。

  • ネイティブ API の制限により、すべてのグローバリゼーション API がハイブリッド モードでサポートされるわけではありません。
  • サポートされている API の中には、動作が異なるものもあります。

アプリケーションが影響を受けるかどうかを確認するには、動作の違い を参照してください。

ソースによって生成された COM 相互運用機能

.NET 8 には、COM インターフェイスとの相互運用をサポートする新しいソース ジェネレーターが含まれています。 GeneratedComInterfaceAttribute を使用して、インターフェイスをソース ジェネレーターの COM インターフェイスとしてマークできます。 ソース ジェネレーターは、C# コードからアンマネージ コードへの呼び出しを有効にするコードを生成します。 また、アンマネージ コードから C# への呼び出しを有効にするコードも生成されます。 このソース ジェネレーターは LibraryImportAttributeと統合され、GeneratedComInterfaceAttribute を持つ型をパラメーターとして使用し、LibraryImport属性付きメソッドで戻り値の型を使用できます。

using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

[GeneratedComInterface]
[Guid("5401c312-ab23-4dd3-aa40-3cb4b3a4683e")]
partial interface IComInterface
{
    void DoWork();
}

internal partial class MyNativeLib
{
    [LibraryImport(nameof(MyNativeLib))]
    public static partial void GetComInterface(out IComInterface comInterface);
}

ソース ジェネレーターでは、GeneratedComInterfaceAttribute 属性を持つインターフェイスを実装する型をアンマネージ コードに渡せるように、新しい GeneratedComClassAttribute 属性もサポートされています。 ソース ジェネレーターは、インターフェイスを実装し、マネージド実装への呼び出しを転送する COM オブジェクトを公開するために必要なコードを生成します。

GeneratedComInterfaceAttribute 属性を持つインターフェイスのメソッドは、LibraryImportAttributeと同じ型をすべてサポートし、LibraryImportAttribute では、GeneratedComInterface属性付き型と GeneratedComClass属性付き型をサポートするようになりました。

C# コードで GeneratedComInterface属性付きインターフェイスのみを使用して、アンマネージ コードから COM オブジェクトをラップするか、C# のマネージド オブジェクトをラップしてアンマネージ コードに公開する場合は、Options プロパティのオプションを使用して、生成されるコードをカスタマイズできます。 これらのオプションは、使用されないことがわかっているシナリオにマーシャラーを記述する必要がないことを意味します。

ソース ジェネレーターは、新しい StrategyBasedComWrappers 型を使用して、COM オブジェクト ラッパーとマネージド オブジェクト ラッパーを作成および管理します。 この新しい型は、COM 相互運用に期待される .NET ユーザー エクスペリエンスを提供しながら、上級ユーザー向けのカスタマイズ ポイントを提供します。 アプリケーションに COM から型を定義するための独自のメカニズムがある場合、またはソース生成 COM で現在サポートされていないシナリオをサポートする必要がある場合は、新しい StrategyBasedComWrappers 型を使用して、シナリオに不足している機能を追加し、COM 型と同じ .NET ユーザー エクスペリエンスを得ることを検討してください。

Visual Studio を使用している場合、新しいアナライザーとコード修正により、既存の COM 相互運用コードを変換してソース生成相互運用機能を使用することが容易になります。 ComImportAttributeを持つ各インターフェイスの横にある電球には、ソースによって生成された相互運用機能に変換するオプションが用意されています。 この修正により、GeneratedComInterfaceAttribute 属性を使用するようにインターフェイスが変更されます。 また、GeneratedComInterfaceAttributeを持つインターフェイスを実装するすべてのクラスの横に、電球には、GeneratedComClassAttribute 属性を型に追加するオプションが用意されています。 型が変換されたら、LibraryImportAttributeを使用するように DllImport メソッドを移動できます。

制限

COM ソース ジェネレーターは、アパートメント アフィニティ、new キーワードを使用した COM CoClass のアクティベーション、および以下の API をサポートしていません。

  • IDispatch ベースのインターフェイス。
  • IInspectable ベースのインターフェイス。
  • COM プロパティとイベント。

構成バインド ソース ジェネレーター

.NET 8 には、ASP.NET Core で AOT とトリムフレンドリな 構成 を提供するソース ジェネレーターが導入されています。 ジェネレーターは、既存のリフレクション ベースの実装の代替手段です。

ソース ジェネレーターは、型情報を取得するための Configure(TOptions)Bind、および Get 呼び出しをプローブします。 プロジェクトでジェネレーターが有効になっている場合、コンパイラは、既存のリフレクション ベースのフレームワーク実装よりも生成されたメソッドを暗黙的に選択します。

ジェネレーターを使用するためにソース コードを変更する必要はありません。 AOT コンパイル Web アプリでは既定で有効になり、PublishTrimmedtrue (.NET 8 以降のアプリ) に設定されている場合に有効になります。 他の種類のプロジェクトの場合、ソース ジェネレーターは既定ではオフになっていますが、EnableConfigurationBindingGenerator プロパティをプロジェクト ファイルに true に設定することでオプトインできます。

<PropertyGroup>
    <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>

次のコードは、バインダーを呼び出す例を示しています。

public class ConfigBindingSG
{
    static void RunIt(params string[] args)
    {
        WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
        IConfigurationSection section = builder.Configuration.GetSection("MyOptions");

        // !! Configure call - to be replaced with source-gen'd implementation
        builder.Services.Configure<MyOptions>(section);

        // !! Get call - to be replaced with source-gen'd implementation
        MyOptions? options0 = section.Get<MyOptions>();

        // !! Bind call - to be replaced with source-gen'd implementation
        MyOptions options1 = new();
        section.Bind(options1);

        WebApplication app = builder.Build();
        app.MapGet("/", () => "Hello World!");
        app.Run();
    }

    public class MyOptions
    {
        public int A { get; set; }
        public string S { get; set; }
        public byte[] Data { get; set; }
        public Dictionary<string, string> Values { get; set; }
        public List<MyClass> Values2 { get; set; }
    }

    public class MyClass
    {
        public int SomethingElse { get; set; }
    }
}

コア .NET ライブラリ

このセクションには、次のサブトピックが含まれています。

反射

.NET 5 で 関数ポインターが導入されましたが、その時点では、対応するリフレクションのサポートは追加されませんでした。 関数ポインターに対して typeof またはリフレクション (たとえば、それぞれ typeof(delegate*<void>()) または FieldInfo.FieldType) を使用すると、IntPtr が返されました。 .NET 8 以降では、代わりに System.Type オブジェクトが返されます。 この型は、呼び出し規則、戻り値の型、パラメーターなど、関数ポインター メタデータへのアクセスを提供します。

手記

関数への物理アドレスである関数ポインター インスタンスは、引き続き IntPtrとして表されます。 リフレクションの種類のみが変更されました。

新しい機能は現在、CoreCLR ランタイムと MetadataLoadContextでのみ実装されています。

IsFunctionPointerSystem.Reflection.PropertyInfoSystem.Reflection.FieldInfoSystem.Reflection.ParameterInfoなど、System.Typeに新しい API が追加されました。 次のコードは、リフレクションに新しい API の一部を使用する方法を示しています。

using System;
using System.Reflection;

// Sample class that contains a function pointer field.
public unsafe class UClass
{
    public delegate* unmanaged[Cdecl, SuppressGCTransition]<in int, void> _fp;
}

internal class FunctionPointerReflection
{
    public static void RunIt()
    {
        FieldInfo? fieldInfo = typeof(UClass).GetField(nameof(UClass._fp));

        // Obtain the function pointer type from a field.
        Type? fpType = fieldInfo?.FieldType;

        // New methods to determine if a type is a function pointer.
        Console.WriteLine(
        $"IsFunctionPointer: {fpType?.IsFunctionPointer}");
        Console.WriteLine(
            $"IsUnmanagedFunctionPointer: {fpType?.IsUnmanagedFunctionPointer}");

        // New methods to obtain the return and parameter types.
        Console.WriteLine($"Return type: {fpType?.GetFunctionPointerReturnType()}");

        if (fpType is not null)
        {
            foreach (Type parameterType in fpType.GetFunctionPointerParameterTypes())
            {
                Console.WriteLine($"Parameter type: {parameterType}");
            }
        }

        // Access to custom modifiers and calling conventions requires a "modified type".
        Type? modifiedType = fieldInfo?.GetModifiedFieldType();

        // A modified type forwards most members to its underlying type.
        Type? normalType = modifiedType?.UnderlyingSystemType;

        if (modifiedType is not null)
        {
            // New method to obtain the calling conventions.
            foreach (Type callConv in modifiedType.GetFunctionPointerCallingConventions())
            {
                Console.WriteLine($"Calling convention: {callConv}");
            }
        }

        // New method to obtain the custom modifiers.
        Type[]? modifiers =
            modifiedType?.GetFunctionPointerParameterTypes()[0].GetRequiredCustomModifiers();

        if (modifiers is not null)
        {
            foreach (Type modreq in modifiers)
            {
                Console.WriteLine($"Required modifier for first parameter: {modreq}");
            }
        }
    }
}

前の例では、次の出力が生成されます。

IsFunctionPointer: True
IsUnmanagedFunctionPointer: True
Return type: System.Void
Parameter type: System.Int32&
Calling convention: System.Runtime.CompilerServices.CallConvSuppressGCTransition
Calling convention: System.Runtime.CompilerServices.CallConvCdecl
Required modifier for first parameter: System.Runtime.InteropServices.InAttribute

シリアル化

.NET 8 では、System.Text.Json のシリアル化と逆シリアル化の機能に多くの改善が加えられました。 たとえば、POCO に含まれていない JSON プロパティの処理をカスタマイズできます。

次のセクションでは、その他のシリアル化の機能強化について説明します。

一般的な JSON シリアル化の詳細については、「.NET での JSON シリアル化と逆シリアル化の」を参照してください。

その他の種類の組み込みサポート

シリアライザーには、次の追加型のサポートが組み込まれています。

  • HalfInt128、および UInt128 数値型。

    Console.WriteLine(JsonSerializer.Serialize(
        [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ]
    ));
    // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
    
  • Memory<T> 値および ReadOnlyMemory<T> 値。 byte 値は Base64 文字列にシリアル化され、その他の型は JSON 配列にシリアル化されます。

    JsonSerializer.Serialize<ReadOnlyMemory<byte>>(new byte[] { 1, 2, 3 }); // "AQID"
    JsonSerializer.Serialize<Memory<int>>(new int[] { 1, 2, 3 }); // [1,2,3]
    

ソース ジェネレーター

.NET 8 には、リフレクション ベースのシリアライザーと同等の ネイティブ AOT エクスペリエンスを実現することを目的とした System.Text.Json ソース ジェネレーター の機能強化が含まれています。 例えば:

  • ソース ジェネレーターでは、required プロパティと init プロパティを使用した型のシリアル化がサポートされるようになりました。 これらはどちらも、リフレクション ベースのシリアル化で既にサポートされていました。

  • ソース生成コードの書式設定が改善されました。

  • JsonSourceGenerationOptionsAttribute 機能は JsonSerializerOptionsと同等です。 詳細については、「オプションの指定 (ソース生成)を参照してください。

  • その他の診断 (SYSLIB1034SYSLIB1039など)。

  • 無視またはアクセスできないプロパティの種類は含めないでください。

  • 任意の型の種類での入れ子の JsonSerializerContext 宣言のサポート。

  • 弱く型指定されたソース生成シナリオでのコンパイラ生成型、または unspeakable 型のサポート。 コンパイラによって生成された型はソース ジェネレーターで明示的に指定できないため、System.Text.Json は実行時に最も近い先祖解決を実行するようになりました。 この解決により、値をシリアル化する最も適切なスーパータイプが決定されます。

  • 新しいコンバーターの種類 JsonStringEnumConverter<TEnum>。 既存の JsonStringEnumConverter クラスは、ネイティブ AOT ではサポートされていません。 列挙型には次のように注釈を付けることができます。

    [JsonConverter(typeof(JsonStringEnumConverter<MyEnum>))]
    public enum MyEnum { Value1, Value2, Value3 }
    
    [JsonSerializable(typeof(MyEnum))]
    public partial class MyContext : JsonSerializerContext { }
    

    詳細については、「列挙型フィールドを文字列としてシリアル化 を参照してください。

  • 新しい JsonConverter.Type プロパティを使用すると、非ジェネリック JsonConverter インスタンスの型を検索できます。

    Dictionary<Type, JsonConverter> CreateDictionary(IEnumerable<JsonConverter> converters)
        => converters.Where(converter => converter.Type != null)
                     .ToDictionary(converter => converter.Type!);
    

    このプロパティは、JsonConverterFactory インスタンスの場合は null を、JsonConverter<T> インスタンスの場合は typeof(T) を返すため、null 許容プロパティです。

チェーン ソース ジェネレーター

JsonSerializerOptions クラスには、既存の TypeInfoResolver プロパティを補完する新しい TypeInfoResolverChain プロパティが含まれています。 これらのプロパティは、ソース ジェネレーターをチェーンするためのコントラクトのカスタマイズで使用されます。 新しいプロパティを追加すると、1 つの呼び出しサイトですべてのチェーン コンポーネントを指定する必要がなく、ファクトの後に追加できます。 TypeInfoResolverChain では、チェーンをイントロスペクトしたり、チェーンからコンポーネントを削除したりすることもできます。 詳細については、「ソース ジェネレーターを結合する」を参照してください。

さらに、JsonSerializerOptions.AddContext<TContext>() は廃止されました。 これは、TypeInfoResolverTypeInfoResolverChain のプロパティに置き換えられます。 詳細については、SYSLIB0049を参照してください。

インターフェイス階層

.NET 8 では、インターフェイス階層からプロパティをシリアル化するためのサポートが追加されています。

次のコードは、すぐに実装されたインターフェイスとその基本インターフェイスの両方のプロパティをシリアル化する例を示しています。

public static void InterfaceHierarchies()
{
    IDerived value = new DerivedImplement { Base = 0, Derived = 1 };
    string json = JsonSerializer.Serialize(value);
    Console.WriteLine(json); // {"Derived":1,"Base":0}
}

public interface IBase
{
    public int Base { get; set; }
}

public interface IDerived : IBase
{
    public int Derived { get; set; }
}

public class DerivedImplement : IDerived
{
    public int Base { get; set; }
    public int Derived { get; set; }
}

名前付けポリシー

JsonNamingPolicy には、snake_case (アンダースコア付き) と kebab-case (ハイフン) プロパティ名の変換用の新しい名前付けポリシーが含まれています。 既存の JsonNamingPolicy.CamelCase ポリシーと同様に、次のポリシーを使用します。

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
JsonSerializer.Serialize(new { PropertyName = "value" }, options);
// { "property_name" : "value" }

詳細については、「組み込みの名前付けポリシーを使用する」を参照してください。

読み取り専用プロパティ

読み取り専用のフィールドまたはプロパティ (つまり、set アクセサーがないフィールド) に逆シリアル化できるようになりました。

このサポートをグローバルにオプトインするには、新しいオプション PreferredObjectCreationHandlingJsonObjectCreationHandling.Populateに設定します。 互換性が懸念される場合は、プロパティを設定する特定の型または個々のプロパティに [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] 属性を配置することで、機能をより詳細に有効にすることもできます。

たとえば、2 つの読み取り専用プロパティを持つ CustomerInfo 型に逆シリアル化する次のコードを考えてみましょう。

public static void ReadOnlyProperties()
{
    CustomerInfo customer = JsonSerializer.Deserialize<CustomerInfo>("""
        { "Names":["John Doe"], "Company":{"Name":"Contoso"} }
        """)!;

    Console.WriteLine(JsonSerializer.Serialize(customer));
}

class CompanyInfo
{
    public required string Name { get; set; }
    public string? PhoneNumber { get; set; }
}

[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
class CustomerInfo
{
    // Both of these properties are read-only.
    public List<string> Names { get; } = new();
    public CompanyInfo Company { get; } = new()
    {
        Name = "N/A",
        PhoneNumber = "N/A"
    };
}

.NET 8 より前では、入力値は無視され、Names プロパティと Company プロパティは既定値を保持していました。

{"Names":[],"Company":{"Name":"N/A","PhoneNumber":"N/A"}}

ここで、入力値を使用して、逆シリアル化中に読み取り専用プロパティを設定します。

{"Names":["John Doe"],"Company":{"Name":"Contoso","PhoneNumber":"N/A"}}

設定の逆シリアル化動作の詳細については、「初期化されたプロパティを設定する」を参照してください。

リフレクション ベースの既定値を無効にする

リフレクションベースのシリアライザーの使用を既定で無効にすることができるようになりました。 この無効化は、特にトリミングされたアプリやネイティブ AOT アプリにおいて、使用されていないリフレクション コンポーネントが誤ってルート化されることを回避するのに役立ちます。 既定のリフレクション ベースのシリアル化を無効にするには、JsonSerializerOptions 引数を JsonSerializer のシリアル化および逆シリアル化メソッドに渡す必要があります。プロジェクト ファイルで JsonSerializerIsReflectionEnabledByDefault MSBuild プロパティを false に設定します。

新しい IsReflectionEnabledByDefault API を使用して、機能スイッチの値を確認します。 System.Text.Jsonを基盤として構築しているライブラリの開発者であれば、リフレクションコンポーネントを誤ってルート化することなく、プロパティを利用してデフォルト設定を行うことができます。

詳細については、「リフレクションの既定値を無効にする」を参照してください。

新しい JsonNode API メソッド

JsonNode 型と System.Text.Json.Nodes.JsonArray 型には、次の新しいメソッドが含まれています。

public partial class JsonNode
{
    // Creates a deep clone of the current node and all its descendants.
    public JsonNode DeepClone();

    // Returns true if the two nodes are equivalent JSON representations.
    public static bool DeepEquals(JsonNode? node1, JsonNode? node2);

    // Determines the JsonValueKind of the current node.
    public JsonValueKind GetValueKind(JsonSerializerOptions options = null);

    // If node is the value of a property in the parent
    // object, returns its name.
    // Throws InvalidOperationException otherwise.
    public string GetPropertyName();

    // If node is the element of a parent JsonArray,
    // returns its index.
    // Throws InvalidOperationException otherwise.
    public int GetElementIndex();

    // Replaces this instance with a new value,
    // updating the parent object/array accordingly.
    public void ReplaceWith<T>(T value);

    // Asynchronously parses a stream as UTF-8 encoded data
    // representing a single JSON value into a JsonNode.
    public static Task<JsonNode?> ParseAsync(
        Stream utf8Json,
        JsonNodeOptions? nodeOptions = null,
        JsonDocumentOptions documentOptions = default,
        CancellationToken cancellationToken = default);
}

public partial class JsonArray
{
    // Returns an IEnumerable<T> view of the current array.
    public IEnumerable<T> GetValues<T>();
}

非公開メンバー

JsonIncludeAttribute および JsonConstructorAttribute 属性注釈を使用して、特定の型のシリアル化コントラクトに非パブリック メンバーをオプトインできます。

public static void NonPublicMembers()
{
    string json = JsonSerializer.Serialize(new MyPoco(42));
    Console.WriteLine(json);
    // {"X":42}

    JsonSerializer.Deserialize<MyPoco>(json);
}

public class MyPoco
{
    [JsonConstructor]
    internal MyPoco(int x) => X = x;

    [JsonInclude]
    internal int X { get; }
}

詳細については、「不変型と非パブリック メンバーとアクセサーを使用する」を参照してください。

ストリーミング逆シリアル化 API

.NET 8 には、GetFromJsonAsAsyncEnumerableなどの新しい IAsyncEnumerable<T> ストリーミング逆シリアル化拡張メソッドが含まれています。 HttpClientJsonExtensions.GetFromJsonAsyncなど、Task<TResult>を返す同様のメソッドが存在しています。 新しい拡張メソッドはストリーミング API を呼び出し、IAsyncEnumerable<T>を返します。

次のコードは、新しい拡張メソッドを使用する方法を示しています。

public async static void StreamingDeserialization()
{
    const string RequestUri = "https://api.contoso.com/books";
    using var client = new HttpClient();
    IAsyncEnumerable<Book?> books = client.GetFromJsonAsAsyncEnumerable<Book>(RequestUri);

    await foreach (Book? book in books)
    {
        Console.WriteLine($"Read book '{book?.title}'");
    }
}

public record Book(int id, string title, string author, int publishedYear);

WithAddedModifier 拡張メソッド

新しい WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) 拡張メソッドを使用すると、任意の IJsonTypeInfoResolver インスタンスのシリアル化コントラクトに変更を簡単に導入できます。

var options = new JsonSerializerOptions
{
    TypeInfoResolver = MyContext.Default
        .WithAddedModifier(static typeInfo =>
        {
            foreach (JsonPropertyInfo prop in typeInfo.Properties)
            {
                prop.Name = prop.Name.ToUpperInvariant();
            }
        })
};

新しい JsonContent.Create オーバーロード

トリミング セーフコントラクトまたはソース生成コントラクトを使用して JsonContent インスタンスを作成できるようになりました。 新しいメソッドは次のとおりです。

var book = new Book(id: 42, "Title", "Author", publishedYear: 2023);
HttpContent content = JsonContent.Create(book, MyContext.Default.Book);

public record Book(int id, string title, string author, int publishedYear);

[JsonSerializable(typeof(Book))]
public partial class MyContext : JsonSerializerContext
{
}

JsonSerializerOptions インスタンスを固定する

次の新しいメソッドを使用すると、JsonSerializerOptions インスタンスを固定するタイミングを制御できます。

  • JsonSerializerOptions.MakeReadOnly()

    このオーバーロードはトリムセーフに設計されているため、オプションのインスタンスがリゾルバーで設定されていない場合は例外が発生します。

  • JsonSerializerOptions.MakeReadOnly(Boolean)

    このオーバーロードに true を渡すと、オプション インスタンスに既定のリフレクション リゾルバーが設定されます (存在しない場合)。 このメソッドは RequiresUnreferenceCode/RequiresDynamicCode マークされているため、ネイティブ AOT アプリケーションには適さない。

新しい IsReadOnly プロパティを使用すると、オプション インスタンスが固定されているかどうかを確認できます。

時間の抽象化

新しい TimeProvider クラスと ITimer インターフェイスにより、時間抽象化 機能が追加され、テスト シナリオで時間をモックすることができます。 さらに、時間抽象化を使用して、Task.DelayTask.WaitAsyncを使用して時間の進行に依存する Task 操作をモックすることができます。 時間抽象化では、次の重要な時間操作がサポートされます。

  • ローカル時刻と UTC 時刻を取得する
  • パフォーマンスを測定するためのタイムスタンプを取得する
  • タイマーを作成する

次のコード スニペットは、いくつかの使用例を示しています。

// Get system time.
DateTimeOffset utcNow = TimeProvider.System.GetUtcNow();
DateTimeOffset localNow = TimeProvider.System.GetLocalNow();

TimerCallback callback = s => ((State)s!).Signal();

// Create a timer using the time provider.
ITimer timer = _timeProvider.CreateTimer(
    callback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);

// Measure a period using the system time provider.
long providerTimestamp1 = TimeProvider.System.GetTimestamp();
long providerTimestamp2 = TimeProvider.System.GetTimestamp();

TimeSpan period = _timeProvider.GetElapsedTime(providerTimestamp1, providerTimestamp2);
// Create a time provider that works with a
// time zone that's different than the local time zone.
private class ZonedTimeProvider(TimeZoneInfo zoneInfo) : TimeProvider()
{
    private readonly TimeZoneInfo _zoneInfo = zoneInfo ?? TimeZoneInfo.Local;

    public override TimeZoneInfo LocalTimeZone => _zoneInfo;

    public static TimeProvider FromLocalTimeZone(TimeZoneInfo zoneInfo) =>
        new ZonedTimeProvider(zoneInfo);
}

UTF8 の機能強化

型の文字列のような表現を宛先スパンに書き出せるようにするには、型に新しい IUtf8SpanFormattable インターフェイスを実装します。 この新しいインターフェイスは ISpanFormattableと密接に関連していますが、UTF16 や Span<char>ではなく UTF8 と Span<byte> を対象とします。

IUtf8SpanFormattable は、stringSpan<char>、または Span<byte>のいずれをターゲットとするかに関係なく、まったく同じ共有ロジックを使用して、すべてのプリミティブ型 (その他) に実装されています。 すべての形式 (新しい "B" バイナリ指定子を含む) とすべてのカルチャが完全にサポートされています。 つまり、ByteComplexCharDateOnlyDateTimeDateTimeOffsetDecimalDoubleGuidHalfIPAddressIPNetworkInt16Int32Int64Int128IntPtrNFloatSByteSingleRuneTimeOnlyTimeSpanUInt16UInt32UInt64UInt128UIntPtr、そして Versionから直接UTF8に書式設定できるようになりました。

新しい Utf8.TryWrite メソッドは、UTF16 ベースの既存の MemoryExtensions.TryWrite メソッドに対応する UTF8 ベースのメソッドを提供します。 挿入文字列構文を使用して、複雑な式を UTF8 バイトのスパンに直接書式設定できます。次に例を示します。

static bool FormatHexVersion(
    short major,
    short minor,
    short build,
    short revision,
    Span<byte> utf8Bytes,
    out int bytesWritten) =>
    Utf8.TryWrite(
        utf8Bytes,
        CultureInfo.InvariantCulture,
        $"{major:X4}.{minor:X4}.{build:X4}.{revision:X4}",
        out bytesWritten);

この実装では、書式値の IUtf8SpanFormattable を認識し、その実装を使用して UTF8 表現を宛先スパンに直接書き込みます。

実装では、新しい Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32) メソッドも使用されます。このメソッドは、対応する Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32) と共に、宛先スパンへのエンコードとデコードをサポートします。 スパンが結果の状態を保持するのに十分な長さでない場合、メソッドは例外をスローするのではなく、false を返します。

ランダム性を操作するためのメソッド

System.Random 型と System.Security.Cryptography.RandomNumberGenerator 型では、ランダム性を操作するための 2 つの新しいメソッドが導入されています。

GetItems<T>()

新しい System.Random.GetItems メソッドと System.Security.Cryptography.RandomNumberGenerator.GetItems メソッドを使用すると、入力セットから指定した数の項目をランダムに選択できます。 次の例は、System.Random.GetItems<T>() (Random.Shared プロパティによって提供されるインスタンス) を使用して、31 個の項目を配列にランダムに挿入する方法を示しています。 この例は、プレイヤーが色付きのボタンのシーケンスを覚えておく必要がある "Simon" のゲームで使用できます。

private static ReadOnlySpan<Button> s_allButtons = new[]
{
    Button.Red,
    Button.Green,
    Button.Blue,
    Button.Yellow,
};

// ...

Button[] thisRound = Random.Shared.GetItems(s_allButtons, 31);
// Rest of game goes here ...

Shuffle<T>()

新しい Random.Shuffle メソッドと RandomNumberGenerator.Shuffle<T>(Span<T>) メソッドを使用すると、スパンの順序をランダム化できます。 これらのメソッドは、機械学習のトレーニング バイアスを減らすのに役立ちます (そのため、最初の方法は必ずしもトレーニングではなく、最後の処理は常にテストされます)。

YourType[] trainingData = LoadTrainingData();
Random.Shared.Shuffle(trainingData);

IDataView sourceData = mlContext.Data.LoadFromEnumerable(trainingData);

DataOperationsCatalog.TrainTestData split = mlContext.Data.TrainTestSplit(sourceData);
model = chain.Fit(split.TrainSet);

IDataView predictions = model.Transform(split.TestSet);
// ...

パフォーマンスに重点を置いた型

.NET 8 では、アプリのパフォーマンス向上を目的とした新しい種類がいくつか導入されています。

  • 新しい System.Collections.Frozen 名前空間には、コレクション型の FrozenDictionary<TKey,TValue>FrozenSet<T>が含まれます。 これらの型では、コレクションの作成後にキーと値を変更することはできません。 この要件により、読み取り操作を高速化できます (たとえば、TryGetValue())。 これらの型は、最初の使用時に設定された後、有効期間の長いサービスの間保持されるコレクションに特に役立ちます。次に例を示します。

    private static readonly FrozenDictionary<string, bool> s_configurationData =
        LoadConfigurationData().ToFrozenDictionary();
    
    // ...
    if (s_configurationData.TryGetValue(key, out bool setting) && setting)
    {
        Process();
    }
    
  • MemoryExtensions.IndexOfAny などのメソッドは、渡されたコレクション 内で最初に出現する任意の値を検索します。 新しい System.Buffers.SearchValues<T> 型は、このようなメソッドに渡されるように設計されています。 それに対応して、.NET 8 では、新しい型のインスタンスを受け入れる MemoryExtensions.IndexOfAny などのメソッドの新しいオーバーロードが追加されます。 SearchValues<T> のインスタンスを作成すると、後続の検索を最適化するために必要なすべてのデータが "その時点で" 導出されるため、作業は前もって行われます。

  • 新しい System.Text.CompositeFormat 型は、コンパイル時に不明な書式指定文字列を最適化するのに役立ちます (たとえば、書式設定文字列がリソース ファイルから読み込まれる場合)。 文字列の解析などの作業には前もって少し余分な時間が費やされますが、使用するたびに作業が行われずに済みます。

    private static readonly CompositeFormat s_rangeMessage =
        CompositeFormat.Parse(LoadRangeMessageResource());
    
    // ...
    static string GetMessage(int min, int max) =>
        string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
    
  • 新しい System.IO.Hashing.XxHash3System.IO.Hashing.XxHash128 の種類では、高速 XXH3 および XXH128 ハッシュ アルゴリズムの実装が提供されます。

System.Numerics と System.Runtime.Intrinsics

このセクションでは、System.NumericsSystem.Runtime.Intrinsics 名前空間の機能強化について説明します。

  • Vector256<T>Matrix3x2、および Matrix4x4 によって、.NET 8 でのハードウェア アクセラレーションが向上しました。 たとえば、Vector256<T> は、可能な場合は内部的に 2x Vector128<T> 操作に再実装されました。 これにより、Arm64 などの条件下で Vector128.IsHardwareAccelerated == true のときに Vector256.IsHardwareAccelerated == falseではない場合、一部の関数を部分的に高速化することが可能になります。
  • ハードウェア組み込み関数には、ConstExpected 属性で注釈が付けられます。 これにより、基になるハードウェアで定数が必要な場合や、定数以外の値が予期せずパフォーマンスを損なう可能性がある場合に、ユーザーが認識されるようになります。
  • Lerp(TSelf, TSelf, TSelf)Lerp API が IFloatingPointIeee754<TSelf> に追加され、float (Single)、double (Double)、および Halfに追加されました。 この API を使用すると、2 つの値間の線形補間を効率的かつ正しく実行できます。

Vector512 と AVX-512

.NET Core 3.0 では、x86/x64 用のプラットフォーム固有のハードウェア組み込み API を含むように SIMD サポートが拡張されました。 .NET 5 では Arm64 のサポートが追加され、.NET 7 ではクロスプラットフォームのハードウェア組み込み機能が追加されました。 .NET 8 では、Intel Advanced Vector Extensions 512 (AVX-512) 命令の Vector512<T> とサポートが導入され、SIMD のサポートがさらに強化されています。

具体的には、.NET 8 には AVX-512 の次の主要な機能のサポートが含まれています。

  • 512 ビット ベクター演算
  • 追加の 16 個の SIMD レジスタ
  • 128 ビット、256 ビット、および 512 ビット のベクトルで使用できる追加の手順

機能をサポートするハードウェアがある場合は、Vector512.IsHardwareAcceleratedtrueを報告します。

.NET 8 では、System.Runtime.Intrinsics.X86 名前空間の下にいくつかのプラットフォーム固有のクラスも追加されます。

これらのクラスは、他の命令セット アーキテクチャ (ISA) と同じ一般的な形状に従い、64 ビット プロセスでのみ使用できる命令の IsSupported プロパティと入れ子になった Avx512F.X64 クラスを公開します。 さらに、各クラスには、対応する命令セットの Avx512VL (ベクター長) 拡張を公開する入れ子になった Avx512F.VL クラスがあります。

コードで Vector512固有の命令または Avx512F固有の命令を明示的に使用しない場合でも、新しい AVX-512 サポートの恩恵を受ける可能性があります。 JIT では、Vector128<T> または Vector256<T>を使用するときに、追加のレジスタと命令を暗黙的に利用できます。 基底クラス ライブラリでは、これらのハードウェア組み込み関数は、Span<T> および ReadOnlySpan<T> によって公開されるほとんどの操作と、プリミティブ型に対して公開されている多くの数学 API で内部的に使用されます。

データの検証

System.ComponentModel.DataAnnotations 名前空間には、クラウド ネイティブ サービスの検証シナリオを対象とした新しいデータ検証属性が含まれています。 既存の DataAnnotations 検証コントロールは、フォーム上のフィールドなどの一般的な UI データ入力検証を目的としていますが、新しい属性は、構成オプションなど、ユーザー入力以外のデータを検証するように設計されています。 新しい属性に加えて、新しいプロパティが RangeAttribute 型に追加されました。

新しい API 説明
RangeAttribute.MinimumIsExclusive
RangeAttribute.MaximumIsExclusive
境界を許容範囲に含めるかどうかを指定します。
System.ComponentModel.DataAnnotations.LengthAttribute 文字列またはコレクションの下限と上限の両方を指定します。 たとえば、[Length(10, 20)] には、少なくとも 10 個の要素と、コレクション内の最大 20 個の要素が必要です。
System.ComponentModel.DataAnnotations.Base64StringAttribute 文字列が有効な Base64 表現であることを検証します。
System.ComponentModel.DataAnnotations.AllowedValuesAttribute
System.ComponentModel.DataAnnotations.DeniedValuesAttribute
許可リストと拒否リストをそれぞれ指定します。 たとえば、[AllowedValues("apple", "banana", "mango")]します。

メトリック

新しい API を使用すると、キーと値のペア タグを作成するときに、Meter オブジェクトと Instrument オブジェクトにアタッチできます。 発行されたメトリック測定のアグリゲーターは、タグを使用して集計値を区別できます。

var options = new MeterOptions("name")
{
    Version = "version",
    // Attach these tags to the created meter.
    Tags = new TagList()
    {
        { "MeterKey1", "MeterValue1" },
        { "MeterKey2", "MeterValue2" }
    }
};

Meter meter = meterFactory!.Create(options);

Counter<int> counterInstrument = meter.CreateCounter<int>(
    "counter", null, null, new TagList() { { "counterKey1", "counterValue1" } }
);
counterInstrument.Add(1);

新しい API には次のものが含まれます。

暗号化手法

.NET 8 では、SHA-3 ハッシュ プリミティブのサポートが追加されています。 (SHA-3 は現在、OpenSSL 1.1.1 以降および Windows 11 ビルド 25324 以降を使用する Linux でサポートされています)。SHA-2 が利用可能な API では、SHA-3 の補完機能が提供されるようになりました。 これには、ハッシュの SHA3_256SHA3_384、および SHA3_512 が含まれます。HMAC の HMACSHA3_256HMACSHA3_384、および HMACSHA3_512。アルゴリズムが構成可能なハッシュのための HashAlgorithmName.SHA3_256HashAlgorithmName.SHA3_384、および HashAlgorithmName.SHA3_512。RSA OAEP 暗号化の RSAEncryptionPadding.OaepSHA3_256RSAEncryptionPadding.OaepSHA3_384、および RSAEncryptionPadding.OaepSHA3_512

次の例では、プラットフォームが SHA-3 をサポートしているかどうかを判断するために、SHA3_256.IsSupported プロパティを含む API を使用する方法を示します。

// Hashing example
if (SHA3_256.IsSupported)
{
    byte[] hash = SHA3_256.HashData(dataToHash);
}
else
{
    // ...
}

// Signing example
if (SHA3_256.IsSupported)
{
     using ECDsa ec = ECDsa.Create(ECCurve.NamedCurves.nistP256);
     byte[] signature = ec.SignData(dataToBeSigned, HashAlgorithmName.SHA3_256);
}
else
{
    // ...
}

SHA-3 のサポートは現在、暗号化プリミティブのサポートを目的としています。 上位レベルの構築とプロトコルでは、最初は SHA-3 が完全にサポートされるとは思われません。 これらのプロトコルには、X.509 証明書、SignedXml、COSE が含まれます。

ネットワーキング

HTTPS プロキシのサポート

これまで、HttpClient がサポートしていたプロキシの種類はすべて、「man-in-the-middle」によって、HTTPS URI であってもクライアントが接続しているサイトを確認されることが可能なものでした。 HttpClient では、HTTPS プロキシ サポートされるようになりました。これにより、クライアントとプロキシの間に暗号化されたチャネルが作成されるため、すべての要求を完全なプライバシーで処理できます。

HTTPS プロキシを有効にするには、all_proxy 環境変数を設定するか、WebProxy クラスを使用してプロキシをプログラムで制御します。

Unix: export all_proxy=https://x.x.x.x:3218 Windows: set all_proxy=https://x.x.x.x:3218

WebProxy クラスを使用して、プロキシをプログラムで制御することもできます。

Stream ベースの ZipFile メソッド

.NET 8 には、ディレクトリに含まれるすべてのファイルを収集して zip 圧縮し、結果の zip ファイルを指定されたストリームに格納できる、ZipFile.CreateFromDirectory の新しいオーバーロードが含まれています。 同様に、新しい ZipFile.ExtractToDirectory オーバーロードを使用すると、zip 形式のファイルを含むストリームを提供し、その内容をファイル システムに抽出できます。 新しいオーバーロードは次のとおりです。

namespace System.IO.Compression;

public static partial class ZipFile
{
    public static void CreateFromDirectory(
        string sourceDirectoryName, Stream destination);

    public static void CreateFromDirectory(
        string sourceDirectoryName,
        Stream destination,
        CompressionLevel compressionLevel,
        bool includeBaseDirectory);

    public static void CreateFromDirectory(
        string sourceDirectoryName,
        Stream destination,
        CompressionLevel compressionLevel,
        bool includeBaseDirectory,
    Encoding? entryNameEncoding);

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, bool overwriteFiles) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, Encoding? entryNameEncoding) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles) { }
}

これらの新しい API は、ディスク領域が制約されている場合に役立ちます。これは、中間ステップとしてディスクを使用する必要がないためです。

拡張機能ライブラリ

このセクションには、次のサブトピックが含まれています。

キー付き DI サービス

キー付き依存関係挿入 (DI) サービスは、キーを使用して DI サービスを登録および取得するための手段を提供します。 キーを使用すると、サービスを登録して使用する方法のスコープを設定できます。 新しい API の一部を次に示します。

次の例は、キー付き DI サービスを使用する方法を示しています。

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<BigCacheConsumer>();
builder.Services.AddSingleton<SmallCacheConsumer>();
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
WebApplication app = builder.Build();
app.MapGet("/big", (BigCacheConsumer data) => data.GetData());
app.MapGet("/small", (SmallCacheConsumer data) => data.GetData());
app.MapGet("/big-cache", ([FromKeyedServices("big")] ICache cache) => cache.Get("data"));
app.MapGet("/small-cache", (HttpContext httpContext) => httpContext.RequestServices.GetRequiredKeyedService<ICache>("small").Get("data"));
app.Run();

class BigCacheConsumer([FromKeyedServices("big")] ICache cache)
{
    public object? GetData() => cache.Get("data");
}

class SmallCacheConsumer(IServiceProvider serviceProvider)
{
    public object? GetData() => serviceProvider.GetRequiredKeyedService<ICache>("small").Get("data");
}

public interface ICache
{
    object Get(string key);
}

public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

詳細については、dotnet/runtime#64427を参照してください。

ホステッド ライフサイクル サービス

ホストされるサービスには、アプリケーションのライフサイクル中に実行するためのオプションが追加されました。 IHostedServiceStartAsyncStopAsyncを提供し、今度は IHostedLifecycleService がこれらの追加メソッドを提供します。

これらのメソッドは、それぞれ既存のポイントの前と後に実行されます。

次の例は、新しい API の使用方法を示しています。

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

internal class HostedLifecycleServices
{
    public async static void RunIt()
    {
        IHostBuilder hostBuilder = new HostBuilder();
        hostBuilder.ConfigureServices(services =>
        {
            services.AddHostedService<MyService>();
        });

        using (IHost host = hostBuilder.Build())
        {
            await host.StartAsync();
        }
    }

    public class MyService : IHostedLifecycleService
    {
        public Task StartingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StartAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StartedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StopAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StoppedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StoppingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
    }
}

詳細については、dotnet/runtime#86511 を参照してください。

オプションの検証

ソース ジェネレーター

スタートアップのオーバーヘッドを減らし、検証機能セットを改善するために、検証ロジックを実装するソース コード ジェネレーターを導入しました。 次のコードは、モデルと検証コントロール クラスの例を示しています。

public class FirstModelNoNamespace
{
    [Required]
    [MinLength(5)]
    public string P1 { get; set; } = string.Empty;

    [Microsoft.Extensions.Options.ValidateObjectMembers(
        typeof(SecondValidatorNoNamespace))]
    public SecondModelNoNamespace? P2 { get; set; }
}

public class SecondModelNoNamespace
{
    [Required]
    [MinLength(5)]
    public string P4 { get; set; } = string.Empty;
}

[OptionsValidator]
public partial class FirstValidatorNoNamespace
    : IValidateOptions<FirstModelNoNamespace>
{
}

[OptionsValidator]
public partial class SecondValidatorNoNamespace
    : IValidateOptions<SecondModelNoNamespace>
{
}

アプリで依存関係の挿入を使用する場合は、次のコード例に示すように検証を挿入できます。

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<FirstModelNoNamespace>(
    builder.Configuration.GetSection("some string"));

builder.Services.AddSingleton<
    IValidateOptions<FirstModelNoNamespace>, FirstValidatorNoNamespace>();
builder.Services.AddSingleton<
    IValidateOptions<SecondModelNoNamespace>, SecondValidatorNoNamespace>();

ValidateOptionsResultBuilder 型

.NET 8 では、ValidateOptionsResult オブジェクトの作成を容易にするために、ValidateOptionsResultBuilder 型が導入されています。 重要なのは、このビルダーは複数のエラーの蓄積を可能にします。 以前は、IValidateOptions<TOptions>.Validate(String, TOptions) の実装に必要な ValidateOptionsResult オブジェクトを作成することは困難であり、階層化された検証エラーが発生する場合があります。 複数のエラーが発生した場合、最初のエラーで検証プロセスが停止することがよくあります。

次のコード スニペットは、ValidateOptionsResultBuilderの使用例を示しています。

ValidateOptionsResultBuilder builder = new();
builder.AddError("Error: invalid operation code");
builder.AddResult(ValidateOptionsResult.Fail("Invalid request parameters"));
builder.AddError("Malformed link", "Url");

// Build ValidateOptionsResult object has accumulating multiple errors.
ValidateOptionsResult result = builder.Build();

// Reset the builder to allow using it in new validation operation.
builder.Clear();

LoggerMessageAttribute コンストラクター

LoggerMessageAttribute では、追加のコンストラクター オーバーロードが提供されるようになりました。 以前は、パラメーターなしのコンストラクターか、すべてのパラメーター (イベント ID、ログ レベル、メッセージ) を必要とするコンストラクターを選択する必要がありました。 新しいオーバーロードにより、コードを減らして必要なパラメーターを指定する柔軟性が向上します。 イベント ID を指定しないと、システムによって自動的に生成されます。

public LoggerMessageAttribute(LogLevel level, string message);
public LoggerMessageAttribute(LogLevel level);
public LoggerMessageAttribute(string message);

拡張機能のメトリック

IMeterFactory インターフェイス

新しい IMeterFactory インターフェイスを依存関係挿入 (DI) コンテナーに登録し、それを使用して分離された方法で Meter オブジェクトを作成できます。

既定のメーター ファクトリ実装を使用して、DI コンテナーに IMeterFactory を登録します。

// 'services' is the DI IServiceCollection.
services.AddMetrics();

その後、コンシューマーはメーター ファクトリを取得し、それを使用して新しい Meter オブジェクトを作成できます。

IMeterFactory meterFactory = serviceProvider.GetRequiredService<IMeterFactory>();

MeterOptions options = new MeterOptions("MeterName")
{
    Version = "version",
};

Meter meter = meterFactory.Create(options);

MetricCollector<T> クラス

新しい MetricCollector<T> クラスを使用すると、メトリックの測定値をタイムスタンプと共に記録できます。 さらに、このクラスでは、タイムスタンプを正確に生成するために、選択したタイム プロバイダーを柔軟に使用できます。

const string CounterName = "MyCounter";
DateTimeOffset now = DateTimeOffset.Now;

var timeProvider = new FakeTimeProvider(now);
using var meter = new Meter(Guid.NewGuid().ToString());
Counter<long> counter = meter.CreateCounter<long>(CounterName);
using var collector = new MetricCollector<long>(counter, timeProvider);

Assert.IsNull(collector.LastMeasurement);

counter.Add(3);

// Verify the update was recorded.
Assert.AreEqual(counter, collector.Instrument);
Assert.IsNotNull(collector.LastMeasurement);

Assert.AreSame(collector.GetMeasurementSnapshot().Last(), collector.LastMeasurement);
Assert.AreEqual(3, collector.LastMeasurement.Value);
Assert.AreEqual(now, collector.LastMeasurement.Timestamp);

System.Numerics.Tensors.TensorPrimitives

更新された System.Numerics.Tensors NuGet パッケージには、テンソル操作のサポートを追加する新しい System.Numerics.Tensors.TensorPrimitives 型の API が含まれています。 テンソル プリミティブは、AI や機械学習のようなデータ集中型のワークロードを最適化します。

セマンティック検索や取得拡張生成 (RAG) などの AI ワークロードでは、関連するデータでプロンプトを拡張することで、ChatGPT などの大規模な言語モデルの自然言語機能が拡張されます。 これらのワークロードでは、ベクトルに対する操作 (コサインの類似性 など)、質問に答える最も関連性の高いデータを見つけることが重要です。 TensorPrimitives 型は、ベクター操作用の API を提供します。

詳細については、.NET 8 RC 2 のブログ投稿を参照してください。

ネイティブ AOT のサポート

ネイティブ AOT として発行するオプションは、.NET 7 で最初に導入されました。 Native AOT を使用してアプリを発行すると、ランタイムを必要としない完全に自己完結型のバージョンのアプリが作成されます。すべてが 1 つのファイルに含まれます。 .NET 8 では、ネイティブ AOT 発行に次の機能強化が行われました。

  • macOSで x64 および Arm64 アーキテクチャのサポートを追加します。

  • Linux 上のネイティブ AOT アプリのサイズを最大 50%削減します。 次の表は、.NET 7 と .NET 8 の .NET ランタイム全体を含むネイティブ AOT で発行された "Hello World" アプリのサイズを示しています。

    オペレーティング システム .NET 7 .NET 8
    Linux x64 (-p:StripSymbols=true付き) 3.76 MB 1.84 MB
    Windows x64 2.85 MB 1.77 MB
  • 最適化の基本設定 (サイズまたは速度) を指定できます。 既定では、コンパイラは、アプリケーションのサイズを考慮しながら高速コードを生成することを選択します。 ただし、<OptimizationPreference> MSBuild プロパティを使用して、いずれか一方に対して特別に最適化することができます。 詳細については、「AOT デプロイの最適化」を参照してください。

ネイティブ AOT を使用して iOS に似たプラットフォームをターゲットにする

.NET 8 では、iOS に似たプラットフォームに対するネイティブ AOT サポートを有効にする作業が開始されます。 次のプラットフォームで、ネイティブ AOT を使用して .NET iOS および .NET MAUI アプリケーションをビルドして実行できるようになりました。

  • ios
  • iossimulator
  • maccatalyst
  • tvos
  • tvossimulator

予備テストでは、Mono ではなくネイティブ AOT を使用する .NET iOS アプリでは、ディスク上のアプリ サイズが約 35% 減少することを示しています。 .NET MAUI iOS アプリのディスク上のアプリ サイズは、最大 50%減少します。 さらに、起動時間も速くなります。 .NET iOS アプリの起動時間は約 28% 速くなりますが、.NET MAUI iOS アプリの起動パフォーマンスは Mono に比べて約 50% 優れています。 .NET 8 のサポートは試験的であり、機能全体の最初のステップにすぎません。 詳細については、.NET MAUI の .NET 8 パフォーマンスの向上に関するブログ記事を参照してください。

ネイティブ AOT のサポートは、アプリのデプロイを目的としたオプトイン機能として利用できます。Mono は、アプリの開発とデプロイの既定のランタイムです。 iOS デバイスでネイティブ AOT を使用して .NET MAUI アプリケーションをビルドして実行するには、dotnet workload install maui を使用して .NET MAUI ワークロードをインストールし、dotnet new maui -n HelloMaui してアプリを作成します。 次に、MSBuild プロパティ PublishAot をプロジェクト ファイル内の true に設定します。

<PropertyGroup>
  <PublishAot>true</PublishAot>
</PropertyGroup>

次の例に示すように、必要なプロパティを設定して dotnet publish を実行すると、ネイティブ AOT を使用してアプリがデプロイされます。

dotnet publish -f net8.0-ios -c Release -r ios-arm64  /t:Run

制限

すべての iOS 機能がネイティブ AOT と互換性があるわけではありません。 同様に、iOS で一般的に使用されるすべてのライブラリが NativeAOT と互換性があるわけではありません。 また、ネイティブ AOT デプロイ の既存の制限に加えて、iOS に似たプラットフォームをターゲットとする場合のその他の制限事項の一部を次に示します。

  • ネイティブ AOT の使用は、アプリのデプロイ中にのみ有効になります (dotnet publish)。
  • マネージド コードのデバッグは Mono でのみサポートされます。
  • .NET MAUI フレームワークとの互換性は制限されています。

Android アプリの AOT コンパイル

アプリのサイズを縮小するために、Android を対象とする .NET および .NET MAUI アプリは、リリース モードでビルドされるときに、プロファイルされた事前 (AOT) コンパイル モードを使用します。 プロファイリングされた AOT コンパイルは、通常の AOT コンパイルよりも少ないメソッドに影響します。 .NET 8 では、Android アプリ用の AOT コンパイルをさらにオプトインしてアプリ のサイズをさらに小さくできる <AndroidStripILAfterAOT> プロパティが導入されています。

<PropertyGroup>
  <AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
</PropertyGroup>

既定では、AndroidStripILAfterAOTtrue に設定すると、既定の AndroidEnableProfiledAot 設定がオーバーライドされ、AOT コンパイルされたすべてのメソッドをトリミングできます (ほぼ)。 また、両方のプロパティを trueに明示的に設定することで、プロファイリングされた AOT と IL のストリッピングを一緒に使用することもできます。

<PropertyGroup>
  <AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
  <AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>

クロスビルド Windows アプリ

Windows 以外のプラットフォームで Windows を対象とするアプリをビルドすると、結果の実行可能ファイルは、アプリケーション アイコン、マニフェスト、バージョン情報など、指定された Win32 リソースで更新されるようになりました。

以前は、このようなリソースを使用するには、Windows 上にアプリケーションを構築する必要がありました。 このクロスビルディング サポートのギャップを解決することは、インフラストラクチャの複雑さとリソースの使用の両方に影響を与える重大な問題であるため、一般的な要求でした。

関連項目