.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% 向上します。 約 4600 テストのベンチマーク スイートでは、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);
}

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

GeneratedComInterfaceAttribute 属性を持つインターフェイス上のメソッドは LibraryImportAttribute と同じ型をすべてサポートしており、LibraryImportAttributeGeneratedComInterface 属性付きの型と 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 ソース ジェネレーターでは、アパートメント アフィニティ、COM CoClass をアクティブにするための new キーワードの使用、および次に示す API はサポートされていません。

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

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

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

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

ジェネレーターを使用するためにソース コードを変更する必要はありません。 AOT の Web アプリでは、既定で有効になっています。 その他のプロジェクトの種類の場合、ソース ジェネレーターは既定ではオフになっていますが、次のようにプロジェクト ファイルで 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; }
    }
}

Core .NET ライブラリ

このセクションでは、次のサブトピックについて説明します。

リフレクション

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

Note

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

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

新しい API (IsFunctionPointerSystem.Reflection.PropertyInfoSystem.Reflection.FieldInfoSystem.Reflection.ParameterInfo など) が System.Type に追加されました。 次のコードは、リフレクションに新しい 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 のシリアル化と逆シリアル化の機能に関しては、多くの機能強化が行われています。 たとえば、JSON ペイロードに含まれていないメンバーの処理をカスタマイズすることができます。

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

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

追加の型の組み込みサポート

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

  • HalfInt128UInt128 の数値型。

    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 ソース ジェネレーターの機能強化が含まれています。 次に例を示します。

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

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

  • 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 より前では、入力値は無視され、NamesCompany のプロパティは既定値を保持していました。

{"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 オーバーロード

trim-safe またはソースで生成されたコントラクトを使用して、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" バイナリ指定子を含む) とすべてのカルチャが完全にサポートされています。 つまり、ByteComplexCharDateOnlyDateTimeDateTimeOffsetDecimalDoubleGuidHalfIPAddressIPNetworkInt16Int32Int64Int128IntPtrNFloatSByteSingleRuneTimeOnlyTimeSpanUInt16UInt32UInt64UInt128UIntPtrVersion から 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.RandomSystem.Security.Cryptography.RandomNumberGenerator の型には、ランダムに操作するための 2 つの新しいメソッドが導入されています。

GetItems<T>()

新しい System.Random.GetItemsSystem.Security.Cryptography.RandomNumberGenerator.GetItems のメソッドを使用すると、入力セットから指定した数の項目をランダムに選択できます。 次の例は、(Random.Shared プロパティによって提供されるインスタンスで) System.Random.GetItems<T>() を使用して、配列に 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.ShuffleRandomNumberGenerator.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(optimizeForReads: true);
    
    // ...
    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 などで、Vector256.IsHardwareAccelerated == false 以外の Vector128.IsHardwareAccelerated == true の場合に一部の関数を部分的に高速化できます。
  • ハードウェアの組み込みに、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 ではさらに、Vector512<T>Intel Advanced Vector Extensions 512 (AVX-512) 命令のサポートが導入されたことで、SIMD のサポートがさらに強化されます。

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

  • 512 ビット ベクター操作
  • 追加の 16 個の SIMD レジスタ
  • 128 ビット、256 ビット、512 ビットのベクターに使用できる追加の命令

この機能をサポートするハードウェアがある場合、Vector512.IsHardwareAcceleratedtrue と報告されるようになりました。

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

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

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

データ検証

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

新しい 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 を使うと、MeterInstrument オブジェクトの作成時に、キーと値のペア タグをそれらにアタッチできます。 公開されたメトリック測定のアグリゲーターでは、タグを使って集計値を区別できます。

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 以降を備えた Linux と Windows 11 のビルド 25324 以降でサポートされています)。SHA-2 が利用可能な API では、SHA-3 の補完が提供されるようになりました。 これには、ハッシュの SHA3_256SHA3_384SHA3_512、HMAC の HMACSHA3_256HMACSHA3_384HMACSHA3_512、アルゴリズムが構成可能なハッシュの HashAlgorithmName.SHA3_256HashAlgorithmName.SHA3_384HashAlgorithmName.SHA3_512、RSA OAEP 暗号化の RSAEncryptionPadding.OaepSHA3_256RSAEncryptionPadding.OaepSHA3_384RSAEncryptionPadding.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 がサポートしていたプロキシ タイプはすべて、HTTPS URI であっても、"中間者" がクライアントが接続しているサイトを確認できるようにしていました。 HttpClientHTTPS プロキシをサポートするようになりました。これにより、クライアントとプロキシの間に暗号化されたチャネルが作成され、すべての要求を完全なプライバシーで処理できるようになります。

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 クラスを使用してプロキシをプログラムで制御することもできます。

ストリームベースの ZipFile メソッド

.NET 8 には、ZipFile.CreateFromDirectory の新しいオーバーロードが含まれています。これを使用すると、ディレクトリに含まれるすべてのファイルを収集して ZIP 化してから、結果の ZIP ファイルを指定されたストリームに格納できます。 同様に、新しい 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 インターフェイス

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

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

// '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 パッケージには、テンソル操作のサポートを追加する新しい TensorPrimitives 名前空間に API が含まれています。 テンソル プリミティブは、AI や機械学習のようなデータ集中型のワークロードを最適化します。

セマンティック検索や取得拡張生成 (RAG) などの AI ワークロードでは、関連データでプロンプトを拡張することで、ChatGPT などの大規模な言語モデルの自然言語機能が拡張されます。 これらのワークロードでは、質問に答えるために最も関連性の高いデータを見つけるためのコサイン類似性などのベクトルに対する操作が重要です。 System.Numerics.Tensors.TensorPrimitives パッケージは、ベクトル操作用の API を提供します。つまり、外部依存関係を取得したり、独自の実装を記述したりする必要はありません。

このパッケージは、System.Numerics.Tensors パッケージを置き換えます。

詳細については、「.NET 8 RC 2 の発表」というブログ記事を参照してください。

関連項目