Freigeben über


Neuerungen in der .NET 8 Runtime

In diesem Artikel werden neue Features in der .NET Runtime für .NET 8 beschrieben.

Leistungsverbesserungen

.NET 8 enthält Verbesserungen an der Codegenerierung und Just-in-Time-Kompilierung (JIT):

  • Arm64-Leistungsverbesserungen
  • SIMD-Verbesserungen
  • Unterstützung für AVX-512-ISA-Erweiterungen (siehe Vector512 und AVX-512)
  • Cloudnative Verbesserungen
  • JIT-Durchsatzverbesserungen
  • Schleifen- und allgemeine Optimierungen
  • Optimierter Zugriff auf Felder, die mit ThreadStaticAttribute gekennzeichnet sind
  • Fortlaufende Registerzuordnung. Arm64 verfügt über zwei Anweisungen für die Tabellenvektorsuche, die voraussetzen, dass alle Entitäten in ihren Tupeloperanden in aufeinanderfolgenden Registern enthalten sind.
  • JIT/NativeAOT können jetzt einige Speichervorgänge mit SIMD starten und automatisch vektorisieren (z. B. Vergleichen, Kopieren und Nullen), wenn die Größe zur Kompilierzeit bestimmt werden kann.

Darüber hinaus wurde die dynamische profilgesteuerte Optimierung (Dynamic Profile-Guided Optimization, PGO) verbessert und ist jetzt standardmäßig aktiviert. Sie müssen keine Laufzeitkonfigurationsoption mehr verwenden, um sie zu aktivieren. Die dynamische PGO ist eng mit der mehrstufigen Kompilierung verzahnt, um den Code auf Grundlage der zusätzlichen Instrumentierung in Ebene 0 zu weiter zu optimieren.

Im Durchschnitt erhöht die dynamische PGO die Leistung um etwa 15 %. In einer Benchmarksammlung mit ca. 4.600 Tests ergaben sich bei 23 % Leistungsverbesserungen von mindestens 20 %.

Höherstufung der CodeGen-Struktur

.NET 8 enthält einen neuen physischen Höherstufungs-Optimierungsdurchlauf für CodeGen, der die Fähigkeit von JIT zum Höherstufen von Strukturvariablen generalisiert. Diese Optimierung (auch als skalare Ersetzung von Aggregaten bezeichnet) ersetzt die Felder von Strukturvariablen durch Primitive, die mit JIT dann genauer untersucht und optimiert werden können.

Diese Optimierung wurde für JIT bereits unterstützt, jedoch mit mehreren großen Einschränkungen wie den folgenden:

  • Sie wurde nur für Strukturen mit höchstens vier Feldern unterstützt.
  • Sie wurde nur unterstützt, wenn jedes Feld ein primitiver Typ oder eine einfache, einen primitiven Typ umschließende Struktur war.

Die physische Höherstufung entfernt diese Einschränkungen und behebt dadurch eine Reihe langjähriger JIT-Probleme.

Garbage Collection

In .NET 8 wird eine Funktion zum dynamischen Anpassen des Arbeitsspeichergrenzwerts hinzugefügt. Dies ist in Clouddienstszenarien nützlich, in denen sich die Nachfrage ständig ändert. Aus Kostengründen sollten Dienste den Ressourcenverbrauch je nach Nachfrage hoch- oder herunterskaliert. Wenn ein Dienst eine Abnahme der Nachfrage erkennt, kann er den Ressourcenverbrauch herunterskalieren, indem er seinen Arbeitsspeichergrenzwert reduziert. Bisher führte dies zu Fehlern, da der Garbage Collector (GC) die Änderung nicht kannte und möglicherweise mehr Arbeitsspeicher als den neuen Grenzwert zugeordnet hat. Mit dieser Änderung können Sie die RefreshMemoryLimit()-API aufrufen, um den GC mit dem neuen Arbeitsspeichergrenzwert zu aktualisieren.

Es gelten jedoch einige Einschränkungen, die Sie beachten müssen:

  • Auf 32-Bit-Plattformen (z. B. Windows x86 und Linux ARM) kann .NET keine neuen feststehenden Heapgrenzwerte festlegen, wenn noch keiner vorhanden ist.
  • Die API gibt möglicherweise einen Statuscode ungleich null zurück, der angibt, dass die Aktualisierung fehlerhaft war. Dies kann passieren, wenn das Herunterskalieren zu stark ist und dem GC keinen Spielraum lässt. In diesem Fall sollten Sie GC.Collect(2, GCCollectionMode.Aggressive) aufrufen, um den aktuellen Arbeitsspeicherverbrauch zu verringern, und es dann erneut versuchen.
  • Wenn Sie den Arbeitsspeichergrenzwert über die Größe hinaus hochskalieren, von der der GC ausgeht, dass der Prozess sie während des Startvorgangs verarbeiten kann, ist der Aufruf von RefreshMemoryLimit zwar erfolgreich, kann aber nicht mehr Arbeitsspeicher verwenden, als er als Grenzwert annimmt.

Der folgende Codeausschnitt zeigt, wie Sie eine API aufrufen.

GC.RefreshMemoryLimit();

Sie können auch einige der GC-Konfigurationseinstellungen im Zusammenhang mit dem Arbeitsspeichergrenzwert aktualisieren. Der folgende Codeschnipsel legt den feststehenden Heapgrenzwert auf 100 Mebibytes (MiB) fest:

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

Die API kann InvalidOperationException auslösen, wenn die harte Grenze ungültig ist, z. B. bei negativen Prozentsätzen für den harten Heapgrenzwert und wenn der harte Grenzwert zu niedrig ist. Dies kann passieren, wenn der harte Heapgrenzwert, der bei der Aktualisierung festgelegt wird, entweder aufgrund neuer AppData-Einstellungen oder impliziert durch Änderungen des Containerspeicherlimits niedriger ist als das, was bereits zugesichert wurde.

Globalisierung für mobile Apps

Mobile Apps unter iOS, tvOS und MacCatalyst können sich für einen neuen hybriden Globalisierungsmodus entscheiden, in dem ein weniger umfangreiches ICU-Bundle verwendet wird. Im Hybridmodus werden Globalisierungsdaten teilweise aus dem ICU-Bundle abgerufen und teilweise aus Aufrufen der nativen APIs. Der Hybridmodus kann für alle Gebietsschemas mit Mobilunterstützung verwendet werden.

Der Hybridmodus eignet sich am besten für Apps, die nicht im invarianten Globalisierungsmodus funktionieren und Kulturen verwenden, die aus ICU-Daten auf Mobilgeräten gekürzt wurden. Sie können den Modus auch verwenden, wenn Sie eine kleinere ICU-Datendatei laden möchten. (Die Datei icudt_hybrid.dat ist 34,5 % kleiner als die ICU-Standarddatendatei icudt.dat.)

Um den hybriden Globalisierungsmodus zu verwenden, legen Sie die MSBuild-Eigenschaft HybridGlobalization auf „true“ fest:

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

Es gelten jedoch einige Einschränkungen, die Sie beachten müssen:

  • Aufgrund von Einschränkungen der nativen API werden nicht alle Globalisierungs-APIs im Hybridmodus unterstützt.
  • Einige der unterstützten APIs weisen ein anderes Verhalten auf.

Informationen dazu, ob Ihre Anwendung betroffen ist, finden Sie unter Verhaltensunterschiede.

Quellgenerierte COM-Interop

.NET 8 enthält einen neuen Quellgenerator, der die Zusammenarbeit mit COM-Schnittstellen unterstützt. Sie können „GeneratedComInterfaceAttribute“ verwenden, um eine Schnittstelle als COM-Schnittstelle für den Quellgenerator zu markieren. Der Quellgenerator generiert dann Code, um das Aufrufen von C#-Code in nicht verwalteten Code zu aktivieren. Außerdem wird Code generiert, um das Aufrufen von nicht verwaltetem Code in C# zu aktivieren. Dieser Quellgenerator ist in „LibraryImportAttribute“ integriert, und Sie können Typen mit „GeneratedComInterfaceAttribute“-Parametern und -Rückgabetypen in „LibraryImport“-attributierten Methoden verwenden.

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);
}

Der Quellgenerator unterstützt auch das neue „GeneratedComClassAttribute“-Attribut, damit Sie Typen übergeben können, die Schnittstellen mit dem „GeneratedComInterfaceAttribute“-Attribut für nicht verwalteten Code implementieren. Der Quellgenerator generiert den Code, der zum Verfügbarmachen eines COM-Objekts erforderlich ist, das die Schnittstellen implementiert, und leitet Aufrufe an die verwaltete Implementierung weiter.

Methoden für Schnittstellen mit dem „GeneratedComInterfaceAttribute“-Attribut unterstützen alle gleichen Typen wie „LibraryImportAttribute“, und „LibraryImportAttribute“ unterstützt jetzt „GeneratedComInterface“-und „GeneratedComClass“-attributierte Typen.

Wenn Ihr C#-Code nur eine „GeneratedComInterface“-attributierte Schnittstelle verwendet, um ein COM-Objekt aus nicht verwaltetem Code oder ein verwaltetes Objekt aus C# zu umschließen, um nicht verwalteten Code verfügbar zu machen, können Sie die Optionen in der „Options“-Eigenschaft verwenden, um anzupassen, welcher Code generiert wird. Diese Optionen bedeuten, dass Sie keine Marshaller für Szenarien schreiben müssen, von denen Sie wissen, dass sie nicht verwendet werden.

Der Quellgenerator verwendet den neuen „StrategyBasedComWrappers“-Typ, um die COM-Objekt-Wrapper und die Wrapper für verwaltete Objekte zu erstellen und zu verwalten. Dieser neue Typ übernimmt die Bereitstellung der erwarteten .NET-Benutzererfahrung für die COM-Interop und stellt gleichzeitig Anpassungspunkte für fortgeschrittene Benutzer bereit. Wenn Ihre Anwendung über einen eigenen Mechanismus zum Definieren von Typen aus COM verfügt, oder Sie Szenarien unterstützen müssen, die aus der Quelle generierten COM derzeit nicht unterstützen, sollten Sie den neuen „StrategyBasedComWrappers“-Typ verwenden, um die fehlenden Features für Ihr Szenario hinzuzufügen und die gleiche .NET-Benutzererfahrung für Ihre COM-Typen zu erhalten.

Wenn Sie Visual Studio verwenden, erleichtern neue Analysetools und Codekorrekturen die Konvertierung Ihres vorhandenen COM-Interop-Codes zur Verwendung von aus der Quelle generierter Interop. Neben jeder Schnittstelle, die über „ComImportAttribute“ verfügt, bietet eine Glühbirne die Option zum Konvertieren in aus der Quelle generierter Interop. Mit dem Fix wird die Schnittstelle so geändert, dass sie das „GeneratedComInterfaceAttribute“-Attribut verwendet. Neben jeder Klasse, die eine Schnittstelle mit „GeneratedComInterfaceAttribute“ implementiert, bietet eine Glühbirne die Option zum Hinzufügen des „GeneratedComClassAttribute“-Attributs zum Typ. Nachdem Ihre Typen konvertiert wurden, können Sie Ihre „DllImport“-Methoden verschieben, um „LibraryImportAttribute“ zu verwenden.

Einschränkungen

Der COM-Quellgenerator unterstützt die Apartmentaffinität nicht, indem das „new“-Schlüsselwort zum Aktivieren einer COM-Co-Klasse und die folgenden APIs verwendet werden:

  • IDispatch“-basierte Schnittstellen.
  • IInspectable“-basierte Schnittstellen.
  • COM-Eigenschaften und -Ereignisse.

Quell-Generator für Konfigurationsbindung

.NET 8 führt einen Quellgenerator ein, um die AOT- und trimfreundliche Konfiguration in ASP.NET Core bereitzustellen. Der Generator stellt eine Alternative zur bereits vorhandenen reflexionsbasierten Implementierung dar.

Der Quellgenerator testet auf Configure(TOptions)-, Bind- und Get-Aufrufe, um Typinformationen abzurufen. Wenn der Generator in einem Projekt aktiviert ist, wählt der Compiler implizit generierte Methoden und nicht die bereits vorhandenen reflexionsbasierten Frameworkimplementierungen aus.

Für die Verwendung des Generators sind keine Änderungen am Quellcode erforderlich. Er ist standardmäßig in AOT'd-Web-Apps aktiviert. Bei anderen Projekttypen ist der Quellgenerator standardmäßig deaktiviert. Sie können ihn jedoch aktivieren, indem Sie die „EnableConfigurationBindingGenerator“-Eigenschaft in Ihrer Projektdatei auf „true“ festlegen:

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

Der folgende Code zeigt ein Beispiel für das Aufrufen des Binders.

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-Bibliotheken

Dieser Abschnitt enthält folgende Unterabschnitte:

Spiegelung

Funktionszeiger wurden in .NET 5 eingeführt, jedoch wurde die entsprechende Unterstützung für die Reflexion zu diesem Zeitpunkt nicht hinzugefügt. Wenn Sie z. B. typeof oder die Reflexion für einen Funktionszeiger verwendeten, z. B. typeof(delegate*<void>()) oder FieldInfo.FieldType, wurde IntPtr zurückgegeben. Ab .NET 8 wird stattdessen ein System.Type-Objekt zurückgegeben. Dieser Typ bietet Zugriff auf Funktionszeiger-Metadaten, einschließlich Aufrufkonventionen, Rückgabetyp und Parametern.

Hinweis

Eine Funktionszeigerinstanz, bei der es sich um eine physische Adresse für eine Funktion handelt, wird weiterhin als IntPtr dargestellt. Nur der Reflexionstyp wurde geändert.

Die neue Funktionalität ist derzeit nur in der CoreCLR-Runtime und MetadataLoadContext implementiert.

Neue APIs wurden zu System.Type hinzugefügt, z. B. IsFunctionPointer, und auch zu System.Reflection.PropertyInfo, System.Reflection.FieldInfo und System.Reflection.ParameterInfo. Der folgende Code zeigt, wie einige der neuen APIs für die Reflexion verwendet werden.

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}");
            }
        }
    }
}

Das vorherige Beispiel erzeugt folgende Ausgabe:

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

Serialisierung

Es wurden zahlreiche Verbesserungen an den System.Text.Json-Funktionen zur Serialisierung und Deserialisierung in .NET 8 vorgenommen. Sie können zum Beispiel die Behandlung von Membern anpassen, die sich nicht in den JSON-Nutzdaten befinden.

In den folgenden Abschnitten werden weitere Serialisierungsverbesserungen beschrieben:

Weitere Informationen zur JSON-Serialisierung im Allgemeinen finden Sie unter JSON-Serialisierung und -Deserialisierung in .NET.

Integrierte Unterstützung für zusätzliche Typen

Der Serialisierer weist eine integrierte Unterstützung der folgenden zusätzlichen Typen auf.

  • Numerische Typen Half, Int128 und UInt128.

    Console.WriteLine(JsonSerializer.Serialize(
        [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ]
    ));
    // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
    
  • Werte Memory<T> und ReadOnlyMemory<T>. byte-Werte werden in Base64-Zeichenfolgen und andere Typen in JSON-Arrays serialisiert.

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

Quell-Generator

In .NET 8 wurden Verbesserungen am System.Text.Json-Quellgenerator in Bezug auf Leistung und Zuverlässigkeit vorgenommen. Diese zielen darauf ab, die Native AOT-Erfahrung auf Augenhöhe mit dem reflexionsbasierten Serialisierungsmodul zu bringen. Beispiel:

  • Der Quell-Generator unterstützt jetzt die Serialisierung von Typen mit den Eigenschaften required und init. Beide wurden bereits in der reflexionsbasierten Serialisierung unterstützt.

  • Verbesserte Formatierung des quellcodegenerierten Codes.

  • Featureparität zwischen JsonSourceGenerationOptionsAttribute und JsonSerializerOptions. Weitere Informationen finden Sie unter Angeben von Optionen (Quellgenerierung).

  • Zusätzliche Diagnosemöglichkeiten (z. B. SYSLIB1034 und SYSLIB1039).

  • Schließen Sie keine Typen von ignorierten oder nicht zugänglichen Eigenschaften ein.

  • Unterstützung für das Schachteln von „JsonSerializerContext“-Deklarationen in beliebigen Typarten.

  • Unterstützung für vom Compiler generierte (oder unaussprechliche) Typen in Szenarien mit schwach typisierter Quellengenerierung. Da vom Compiler generierte Typen nicht explizit vom Quellengenerator angegeben werden können, führt System.Text.Json jetzt zur Laufzeit eine Auflösung zum nächsten Vorgänger durch. Diese Auflösung bestimmt den am besten geeigneten übergeordneten Typ, mit dem der Wert serialisiert werden soll.

  • Neuer Konvertertyp „JsonStringEnumConverter<TEnum>“. Die vorhandene Klasse „JsonStringEnumConverter“ wird von Native AOT nicht unterstützt. Sie können den Änderungsverlauf für Ihre Enumerationstypen folgendermaßen einblenden:

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

    Weitere Informationen finden Sie unter Serialisieren von Enumerationsfeldern als Zeichenfolgen.

  • Mit der neuen JsonConverter.Type-Eigenschaft können Sie den Typ einer nicht generischen JsonConverter-Instanz suchen:

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

    Die Eigenschaft ist lässt Nullwerte zu, da sie „null“ für „JsonConverterFactory“-Instanzen und „typeof(T)“ für „JsonConverter<T>“-Instanzen zurückgibt.

Verketten von Quellengeneratoren

Die JsonSerializerOptions-Klasse enthält eine neue TypeInfoResolverChain-Eigenschaft, die die vorhandene TypeInfoResolver-Eigenschaft ergänzt. Diese Eigenschaften werden in der Vertragsanpassung zum Verketten von Quellengeneratoren verwendet. Aufgrund der neuen Eigenschaft müssen Sie nicht mehr alle verketteten Komponenten in einem Aufruf angeben, sondern sie können danach hinzugefügt werden. Mit TypeInfoResolverChain können Sie auch die Kette überprüfen oder Komponenten daraus entfernen. Weitere Informationen finden Sie unter Kombinieren von Quellgeneratoren.

Darüber hinaus ist JsonSerializerOptions.AddContext<TContext>() jetzt veraltet. Sie wurde durch die Eigenschaften TypeInfoResolver und TypeInfoResolverChain ersetzt. Weitere Informationen finden Sie unter SYSLIB0049.

Schnittstellenhierarchien

Ab .NET 8 wird die Serialisierung von Eigenschaften aus Schnittstellenhierarchien unterstützt.

Der folgende Code zeigt ein Beispiel, in dem die Eigenschaften sowohl der sofort implementierten Schnittstelle als auch der zugehörigen Basisschnittstelle serialisiert werden.

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; }
}

Benennungsrichtlinien

JsonNamingPolicy enthält neue Benennungsrichtlinien für die Konvertierung von snake_case (mit Unterstrich)- und kebab-case (mit Bindestrich)-Eigenschaftsnamen. Verwenden Sie diese Richtlinien ähnlich wie die vorhandene JsonNamingPolicy.CamelCase-Richtlinie:

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

Weitere Informationen finden Sie unter Verwenden einer integrierten Benennungsrichtlinie.

Schreibgeschützte Eigenschaften

Sie können jetzt in schreibgeschützte Felder oder Eigenschaften deserialisieren (d. h. in solche, die keine set-Zugriffsmethode haben).

Um diese Unterstützung global zu aktivieren, legen Sie die neue Option PreferredObjectCreationHandling auf JsonObjectCreationHandling.Populate fest. Wenn die Kompatibilität wichtig ist, können Sie die Funktionalität auch präziser aktivieren, indem Sie das [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]-Attribut für bestimmte Typen verwenden, deren Eigenschaften aufgefüllt werden sollen, oder für einzelne Eigenschaften.

Betrachten Sie beispielsweise den folgenden Code, der in einen CustomerInfo-Typ mit zwei schreibgeschützten Eigenschaften deserialisiert.

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"
    };
}

Vor .NET 8 wurden die Eingabewerte ignoriert, und die Eigenschaften Names und Company behielten ihre Standardwerte bei.

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

Nun werden die Eingabewerte verwendet, um die schreibgeschützten Eigenschaften während der Deserialisierung aufzufüllen.

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

Weitere Informationen zum Deserialisierungsverhalten Auffüllen finden Sie unter Auffüllen initialisierter Eigenschaften.

Standardmäßiges Deaktivieren der reflexionsbasierten Serialisierung

Sie können jetzt standardmäßig das reflexionsbasierte Serialisierungsmodul deaktivieren. Diese Deaktivierung ist nützlich, um ein versehentliches Rooten von Reflexionskomponenten zu vermeiden, die nicht einmal verwendet werden, insbesondere in gekürzten und Native AOT-Apps. Wenn Sie die standardmäßige reflexionsbasierte Serialisierung deaktivieren möchten, indem Sie verlangen, dass ein JsonSerializerOptions-Argument an die JsonSerializer-Methoden zur Serialisierung und Deserialisierung übergeben werden muss, legen Sie die JsonSerializerIsReflectionEnabledByDefault-MSBuild-Eigenschaft in Ihrer Projektdatei auf false fest.

Verwenden Sie die neue IsReflectionEnabledByDefault-API, um den Wert des Funktionsschalters zu überprüfen. Wenn Sie eine Bibliothek auf Grundlage von System.Text.Json erstellen, können Sie sich darauf verlassen, dass die Eigenschaft Ihre Standardwerte konfiguriert, ohne versehentlich Reflexionskomponenten zu rooten.

Weitere Informationen finden Sie unter Reflexionsstandards deaktivieren.

Neue JsonNode-API-Methoden

Die Typen JsonNode und System.Text.Json.Nodes.JsonArray enthalten die folgenden neuen Methoden.

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>();
}

Nicht-öffentliche Mitglieder

Sie können nicht öffentliche Member mit den Attributanmerkungen JsonIncludeAttribute und JsonConstructorAttribute in den Serialisierungsvertrag für einen bestimmten Typ aufnehmen.

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; }
}

Weitere Informationen finden Sie unter Verwenden unveränderlicher Typen und nicht öffentlicher Member und Zugriffsmethoden.

Streaming-Deserialisierungs-APIs

.NET 8 enthält neue IAsyncEnumerable<T> Streaming-Deserialisierungserweiterungsmethoden, z. B GetFromJsonAsAsyncEnumerable. Ähnliche Methoden sind vorhanden, die Task<TResult> zurückgegeben, wie beispielsweise HttpClientJsonExtensions.GetFromJsonAsync. Die neuen Erweiterungsmethoden rufen Streaming-APIs auf und geben IAsyncEnumerable<T> zurück.

Der folgende Code zeigt, wie Sie die neuen Erweiterungsmethoden verwenden können.

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);

MitHinzugefügtemModifizierer-Erweiterungsmethode

Mit der neuen Erweiterungsmethode WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) können Sie ganz einfach Änderungen an den Serialisierungsverträgen beliebiger IJsonTypeInfoResolver-Instanzen einführen.

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

Neue JsonContent.Create-Überladung

Sie können jetzt Instanzen mithilfe von Sicherer-Kürzung oder quellgenerierten Verträgen erstellen JsonContent. Die neuen Methoden sind:

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
{
}

Fixieren einer JsonSerializerOptions-Instanz

Mit den folgenden neuen Methoden können Sie steuern, wann eine JsonSerializerOptions-Instanz fixiert wird:

  • JsonSerializerOptions.MakeReadOnly()

    Diese Überladung ist so konzipiert, dass sie kürzungssicher ist und löst daher eine Ausnahme in Fällen aus, in denen die Optionsinstanz nicht mit einem Resolver konfiguriert wurde.

  • JsonSerializerOptions.MakeReadOnly(Boolean)

    Wenn Sie true an diese Überladung übergeben, füllt sie die Optionsinstanz mit dem standardmäßigen Spiegelungsresolver auf, wenn eins fehlt. Diese Methode ist markiert RequiresUnreferenceCode/RequiresDynamicCode und eignet sich daher nicht für native AOT-Anwendungen.

Mit der neuen Eigenschaft IsReadOnly können Sie überprüfen, ob die Optionsinstanz fixiert ist.

Zeitabstraktion

Die neue TimeProvider-Klasse und die ITimer-Schnittstelle fügen eine Funktion für die Zeitabstraktion hinzu, mit der Sie die Zeit in Testszenarien simulieren können. Darüber hinaus können Sie die Zeitabstraktion verwenden, um Task-Vorgänge zu simulieren, die für Task.Delay und Task.WaitAsync den Zeitverlauf benötigen. Die Zeitabstraktion unterstützt die folgenden wichtigen Zeitvorgänge:

  • Abrufen von lokaler und UTC-Zeit
  • Abrufen eines Zeitstempels zum Messen der Leistung
  • Erstellen eines Timers

Der folgende Codeschnipsel zeigt einige Beispiele für die Verwendung.

// 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);
}

Verbesserungen bei UTF8

Wenn Sie das Schreiben einer zeichenfolgenartigen Darstellung Ihres Typs für einen Zielbereich aktivieren möchten, implementieren Sie die neue IUtf8SpanFormattable-Schnittstelle in Ihrem Typ. Diese neue Schnittstelle ist eng mit ISpanFormattable verknüpft, zielt jedoch auf UTF8 und Span<byte> anstelle von UTF16 und Span<char> ab.

IUtf8SpanFormattable wurde für alle primitiven Typen (und andere) mit der exakt gleichen gemeinsamen Logik implementiert, unabhängig davon, ob als Ziel string, Span<char> oder Span<byte> verwendet wird. Dabei werden alle Formate (einschließlich des neuen Binärbezeichners „B“) und alle Kulturen unterstützt. Dies bedeutet, dass Sie jetzt direkt in UTF8 formatieren können in: Byte, Complex, Char, DateOnly, DateTime, DateTimeOffset, Decimal, Double, Guid, Half, IPAddress, IPNetwork, Int16, Int32, Int64, Int128, IntPtr, NFloat, SByte, Single, Rune, TimeOnly, TimeSpan, UInt16, UInt32, UInt64, UInt128, UIntPtr und Version.

Neue Utf8.TryWrite-Methoden bieten eine UTF8-basierte Alternative zu den vorhandenen MemoryExtensions.TryWrite-Methoden, die auf UTF16 basieren. Sie können die Syntax für interpolierte Zeichenfolgen verwenden, um einen komplexen Ausdruck direkt in einen Bereich von UTF8-Bytes zu formatieren, z. B.:

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);

Die Implementierung erkennt IUtf8SpanFormattable in den Formatwerten und verwendet deren Implementierungen, um ihre UTF8-Darstellungen direkt in den Zielbereich zu schreiben.

Außerdem verwendet die Implementierung die neue Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32)-Methode, die zusammen mit ihrer Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32)-Entsprechung die Codierung und Decodierung in einen Zielbereich unterstützt. Wenn der Bereich nicht lang genug ist, um den resultierenden Zustand zu halten, geben die Methoden false zurück anstatt eine Ausnahme auszulösen.

Methoden zum Arbeiten mit Zufälligkeit

Die Typen System.Random und System.Security.Cryptography.RandomNumberGenerator führen zwei neue Methoden zum Arbeiten mit Zufälligkeit ein.

GetItems<T>()

Mit den neuen Methoden System.Random.GetItems und System.Security.Cryptography.RandomNumberGenerator.GetItems können Sie nach dem Zufallsprinzip eine vorgegebene Anzahl von Elementen aus einem Eingabesatz auswählen. Das folgende Beispiel zeigt, wie Sie System.Random.GetItems<T>() verwenden (mit der von der Random.Shared-Eigenschaft bereitgestellten Instanz), um 31 Elemente nach dem Zufallsprinzip in ein Array einzufügen. Dieses Beispiel könnte in einer Partie des Spiels „Senso“ (International: „Simon“) verwendet werden, bei dem sich Spieler eine Abfolge farbiger Tasten merken müssen.

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>()

Mit den neuen Methoden Random.Shuffle und RandomNumberGenerator.Shuffle<T>(Span<T>) können Sie die Reihenfolge eines Bereichs zufällig festlegen („randomisieren“). Diese Methoden sind nützlich, um Trainingsverzerrungen beim maschinellen Lernen zu reduzieren (deshalb ist nicht immer Training das Erste und Testen das Letzte).

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);
// ...

Leistungsorientierte Typen

.NET 8 führt mehrere neue Typen zur Verbesserung der App-Leistung ein.

  • Der neue System.Collections.Frozen-Namespace enthält die Auflistungstypen FrozenDictionary<TKey,TValue> und FrozenSet<T>. Diese Typen lassen nach der Erstellung einer Auflistung keine Änderungen an Schlüsseln und Werten mehr zu. Diese Anforderung ermöglicht schnellere Lesevorgänge (z. B. TryGetValue()). Diese Typen sind besonders nützlich für Sammlungen, die bei der ersten Verwendung aufgefüllt und dann für die Dauer eines langlebigen Diensts erhalten bleiben, z. B.:

    private static readonly FrozenDictionary<string, bool> s_configurationData =
        LoadConfigurationData().ToFrozenDictionary(optimizeForReads: true);
    
    // ...
    if (s_configurationData.TryGetValue(key, out bool setting) && setting)
    {
        Process();
    }
    
  • Methoden wie MemoryExtensions.IndexOfAny suchen nach dem ersten Vorkommen von einem beliebigen Wert in der übergebenen Auflistung. Der neue System.Buffers.SearchValues<T>-Typ soll an solche Methoden übergeben werden. Entsprechend fügt .NET 8 neue Überladungen von Methoden wie MemoryExtensions.IndexOfAny hinzu, die eine Instanz des neuen Typs akzeptieren. Wenn Sie eine Instanz von SearchValues<T> erstellen, werden alle Daten, die für die Optimierung nachfolgender Suchvorgänge erforderlich sind, zu diesem Zeitpunkt abgeleitet, d. h. die Arbeit wird im Voraus erledigt.

  • Der neue System.Text.CompositeFormat-Typ ist nützlich, um Formatzeichenfolgen zu optimieren, die zur Kompilierzeit nicht bekannt sind (z. B. wenn die Formatzeichenfolge aus einer Ressourcendatei geladen wird). Im Vorfeld wird etwas mehr Zeit für die Arbeit aufgewendet, wie das Analysieren der Zeichenfolge, aber es erspart die Ausführung dieser Arbeit bei jeder Verwendung.

    private static readonly CompositeFormat s_rangeMessage =
        CompositeFormat.Parse(LoadRangeMessageResource());
    
    // ...
    static string GetMessage(int min, int max) =>
        string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
    
  • Neue System.IO.Hashing.XxHash3- und System.IO.Hashing.XxHash128-Typen bieten Implementierungen der schnellen Hashalgorithmen XXH3 und XXH128.

System.Numerics und System.Runtime.Intrinsics

In diesem Abschnitt werden Verbesserungen der Namespaces System.Numerics und System.Runtime.Intrinsics behandelt.

  • Vector256<T>, Matrix3x2 und Matrix4x4 haben die Hardwarebeschleunigung in .NET 8 verbessert. So wurde z. B Vector256<T> intern als 2x Vector128<T>-Vorgänge neu implementiert, wann immer möglich. Dies ermöglicht eine partielle Beschleunigung einiger Funktionen, wenn Vector128.IsHardwareAccelerated == true, aber Vector256.IsHardwareAccelerated == false, z. B. auf Arm64.
  • Intrinsische Hardwarekomponenten werden jetzt mit dem ConstExpected-Attribut kommentiert. Dadurch wird sichergestellt, dass Benutzer wissen, wann die zugrunde liegende Hardware eine Konstante erwartet und somit auch, wann ein nicht konstanter Wert die Leistung unerwartet beeinträchtigen kann.
  • Die Lerp(TSelf, TSelf, TSelf)Lerp-API wurde zu IFloatingPointIeee754<TSelf> hinzugefügt und somit zu float (Single), double (Double) und Half. Diese API ermöglicht die effiziente und korrekte Ausführung einer linearen Interpolation zwischen zwei Werten.

Vector512 und AVX-512

In .NET Core 3.0 wurde die SIMD-Unterstützung erweitert, um die plattformspezifischen hardwareinternen APIs für x86 und x64 einzuschließen. In .NET 5 wurde Unterstützung für Arm64 und in .NET 7 wurde Unterstützung für plattformübergreifende Hardwarefunktionen hinzugefügt. In .NET 8 wird die SIMD-Unterstützung durch Einführung von Vector512<T> und Unterstützung von AVX-512-Anweisungen (Intel Advanced Vector Extensions 512) noch einmal erweitert.

Insbesondere bietet .NET 8 Unterstützung für die folgenden wichtigen Features von AVX-512:

  • 512-Bit-Vektorvorgänge
  • Zusätzliche 16 SIMD-Register
  • Zusätzliche Anweisungen für 128-Bit-, 256-Bit- und 512-Bit-Vektoren

Wenn Sie über Hardware verfügen, die diese Funktionalität unterstützt, wird für Vector512.IsHardwareAccelerated jetzt true gemeldet.

Darüber hinaus werden in .NET 8 mehrere plattformspezifische Klassen unter dem System.Runtime.Intrinsics.X86-Namespace hinzugefügt:

Diese Klassen folgen der gleichen allgemeinen Form wie andere Befehlssatzarchitekturen (ISAs): Sie machen eine IsSupported-Eigenschaft und eine geschachtelte Avx512F.X64-Klasse für Anweisungen verfügbar machen, die nur für 64-Bit-Prozesse verfügbar sind. Darüber hinaus verfügt jede Klasse über eine geschachtelte Avx512F.VL-Klasse, die die Avx512VL-Erweiterungen (Vektorlänge) für den entsprechenden Anweisungssatz verfügbar macht.

Auch wenn Sie nicht explizit spezifische Anweisungen für Vector512 oder Avx512F in Ihrem Code verwenden, profitieren Sie wahrscheinlich weiterhin von der neuen AVX-512-Unterstützung. Das JIT kann die zusätzlichen Register und Anweisungen implizit nutzen, wenn Vector128<T> oder Vector256<T> verwendet wird. Die Basisklassenbibliothek verwendet diese hardwareinternen Funktionen intern bei den meisten Vorgängen, die von Span<T> und ReadOnlySpan<T> in vielen der mathematischen APIs für die primitiven Typen verfügbar gemacht werden.

Datenvalidierung

Der System.ComponentModel.DataAnnotations-Namespace enthält neue Datenüberprüfungsattribute für Überprüfungsszenarios in cloudnativen Diensten. Während die bereits vorhandenen DataAnnotations-Validierungssteuerelemente auf die typische Überprüfung des Benutzeroberflächen-Dateneintrags ausgerichtet sind, z. B. Felder in einem Formular, sind die neuen Attribute so konzipiert, dass sie Nicht-Benutzereingabedaten überprüfen, z. B. Konfigurationsoptionen. Zusätzlich zu den neuen Attributen wurden den Typen RangeAttribute und RequiredAttribute neue Eigenschaften hinzugefügt.

Neue API BESCHREIBUNG
RangeAttribute.MinimumIsExclusive
RangeAttribute.MaximumIsExclusive
Gibt an, ob Grenzen im zulässigen Bereich enthalten sind.
System.ComponentModel.DataAnnotations.LengthAttribute Gibt sowohl Unter- als auch Obergrenzen für Zeichenfolgen oder Sammlungen an. [Length(10, 20)] erfordert beispielsweise mindestens 10 Elemente und höchstens 20 Elemente in einer Sammlung.
System.ComponentModel.DataAnnotations.Base64StringAttribute Überprüft, ob eine Zeichenfolge eine gültige Base64-Darstellung ist.
System.ComponentModel.DataAnnotations.AllowedValuesAttribute
System.ComponentModel.DataAnnotations.DeniedValuesAttribute
Geben Sie Zulassungslisten bzw. Ablehnungslisten an. Beispiel: [AllowedValues("apple", "banana", "mango")].

Metriken

Mit neuen APIs können Sie Tags mit Schlüssel-Wert-Paaren an Meter- und Instrument-Objekte bei deren Erstellung anfügen. Aggregatoren veröffentlichter Metrikmessungen können anhand dieser Tags die aggregierten Werte unterscheiden.

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);

Zu den neuen APIs zählen:

Kryptografie

.NET 8 fügt Unterstützung für die SHA-3-Hash-Grundelemente hinzu. (SHA-3 wird derzeit von Linux mit OpenSSL 1.1.1 oder höher und von Windows 11 Build 25324 oder höher unterstützt.) APIs, in denen SHA-2 jetzt verfügbar ist, bieten eine SHA-3-Ergänzung. Diese umfasst SHA3_256, SHA3_384und SHA3_512 für Hashing; HMACSHA3_256, HMACSHA3_384und HMACSHA3_512 für HMAC; HashAlgorithmName.SHA3_256, HashAlgorithmName.SHA3_384und HashAlgorithmName.SHA3_512 für Hashing, bei dem der Algorithmus konfigurierbar ist; und RSAEncryptionPadding.OaepSHA3_256, RSAEncryptionPadding.OaepSHA3_384 und RSAEncryptionPadding.OaepSHA3_512für die RSA-OAEP-Verschlüsselung.

Das folgende Beispiel zeigt, wie Sie die APIs einschließlich der „SHA3_256.IsSupported“-Eigenschaft verwenden, um zu bestimmen, ob die Plattform SHA-3 unterstützt.

// 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-Unterstützung zielt derzeit auf die Unterstützung kryptografischer Grundelemente ab. Von übergeordneten Konstrukten und Protokollen wird zunächst nicht erwartet, dass sie SHA-3 vollständig unterstützen. Diese Protokolle umfassen X.509-Zertifikate, SignedXml, und COSE.

Netzwerk

Unterstützung für HTTPS-Proxy

Bisher ließen die von HttpClient unterstützten Proxytypen einen „Man-in-the-Middle“-Vorgang zu, um festzustellen, mit welchem Standort der Client eine Verbindung herstellt, selbst für HTTPS-URIs. HttpClient unterstützt jetzt HTTPS-Proxy, sodass ein verschlüsselter Kanal zwischen dem Client und dem Proxy erstellt wird, damit alle Anforderungen mit vollständigem Datenschutz verarbeitet werden können.

Um HTTPS-Proxy zu aktivieren, legen Sie die Umgebungsvariable all_proxy fest, oder steuern Sie den Proxy programmgesteuert über die WebProxy-Klasse.

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

Sie können auch die WebProxy-Klasse verwenden, um den Proxy programmgesteuert zu steuern.

Streambasierte ZipFile-Methoden

.NET 8 enthält neue Überladungen von ZipFile.CreateFromDirectory, mit denen Sie alle in einem Verzeichnis enthaltenen Dateien sammeln, zippen und die resultierende ZIP-Datei dann im bereitgestellten Stream speichern können. Auf ähnliche Weise können Sie mit den neuen „ZipFile.ExtractToDirectory“-Überladungen einen Stream bereitstellen, der eine gezippte Datei enthält, und deren Inhalt in das Dateisystem extrahieren. Dies sind die neuen Überladungen:

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) { }
}

Diese neuen APIs können nützlich sein, wenn der Speicherplatz begrenzt ist, da sie die Verwendung des Datenträgers als Zwischenschritt vermeiden.

Erweiterungsbibliotheken

Dieser Abschnitt enthält folgende Unterabschnitte:

DI-Dienste mit Schlüsseln

DI-Dienste (Dependency Injection, Abhängigkeitsinjektion) mit Schlüsseln bieten eine Möglichkeit zum Registrieren und Abrufen von DI-Diensten mithilfe von Schlüsseln. Mithilfe von Schlüsseln können Sie festlegen, in welchem Umfang Ihre Dienste registriert und genutzt werden. Dies sind einige der neuen APIs:

Das folgende Beispiel zeigt, wie Sie DI-Dienste mit Schlüsseln verwenden.

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.";
}

Weitere Informationen finden Sie unter dotnet/runtime#64427.

Gehostete Lebenszyklusdienste

Gehostete Dienste weisen jetzt weitere Optionen für die Ausführung während des Anwendungslebenszyklus auf. IHostedService stellte StartAsync und StopAsync bereit, und jetzt stellt IHostedLifecycleService die folgenden zusätzlichen Methoden bereit:

Diese Methoden werden vor bzw. nach den vorhandenen Punkten ausgeführt.

Das folgende Beispiel zeigt die Verwendung der neuen APIs.

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;
    }
}

Weitere Informationen finden Sie unter dotnet/runtime#86511.

Überprüfung von Optionen

Quell-Generator

Um den Aufwand beim Startup zu reduzieren und die Validierungsfeatures zu verbessern, haben wir einen Quellcodegenerator eingeführt, der die Validierungslogik implementiert. Der folgende Code zeigt Beispielmodelle und Validierungsklassen.

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>
{
}

Wenn Ihre Anwendung Abhängigkeitsinjektion verwendet, können Sie die Validierung wie im folgenden Beispielcode gezeigt einfügen.

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-Typ

In .NET 8 wird der ValidateOptionsResultBuilder-Typ eingeführt, um die Erstellung eines ValidateOptionsResult-Objekts zu unterstützen. Wichtig ist, dass dieser Generator die Akkumulation mehrerer Fehler ermöglicht. Bisher war das Erstellen des ValidateOptionsResult-Objekts für die Implementierung von IValidateOptions<TOptions>.Validate(String, TOptions) schwierig und führte manchmal zu mehrstufigen Validierungsfehlern. Wenn mehrere Fehler aufgetreten sind, wurde der Überprüfungsprozess häufig beim ersten Fehler beendet.

Der folgende Codeausschnitt zeigt ein Beispiel der Verwendung von 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-Konstruktoren

LoggerMessageAttribute bietet jetzt zusätzliche Konstruktorüberladungen. Vorhin mussten Sie sich entweder für den parameterlosen Konstruktor oder für den Konstruktor, der alle Parameter (Ereignis-ID, Protokollebene und Nachricht) erforderte, entscheien. Die neuen Überladungen bieten mehr Flexibilität bei gleichzeitig reduziertem Code bei der Angabe der erforderlichen Parameter. Wenn Sie keine Ereignis-ID angeben, generiert das System automatisch eine.

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

Erweiterungsmetriken

IMeterFactory-Schnittstelle

Sie können die neue IMeterFactory-Schnittstelle in Dependency Injection-Containern (DI) registrieren und mit ihrer Hilfe Meter-Objekte isoliert erstellen.

Registrieren Sie IMeterFactory für den DI-Container unter Verwendung der Standardimplementierung von meterFactory:

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

Consumer können dann meterFactory abrufen und damit ein neues Meter-Objekt erstellen.

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

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

Meter meter = meterFactory.Create(options);

MetricCollector<T>-Klasse

Mit der neuen „MetricCollector<T>“-Klasse können Sie Metrikmessungen mit Zeitstempeln aufzeichnen. Darüber hinaus bietet die Klasse die Flexibilität, einen Zeitanbieter Ihrer Wahl für die präzise Generierung von Zeitstempeln zu nutzen.

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

Das aktualisierte System.Numerics.Tensors NuGet-Paket enthält APIs im neuen TensorPrimitives Namespace, die Unterstützung für Tensorvorgänge hinzufügen. Die Tensorgrundtypen optimieren datenintensive Workloads wie KI und maschinelles Lernen.

KI-Workloads wie die semantische Suche und die Generierung von Abruferweiterungen (RAG) erweitern die natürlichen Sprachfunktionen großer Sprachmodelle wie ChatGPT durch Erweitern von Eingabeaufforderungen mit relevanten Daten. Für diese Arbeitslasten sind Vorgänge auf Vektoren – z. B. Kosinusähnlichkeit, um die relevantesten Daten zu finden, um eine Frage zu beantworten – von entscheidender Bedeutung. Das Paket „System.Numerics.Tensors.TensorPrimitives“ stellt APIs für Vektorvorgänge bereit, was bedeutet, dass Sie keine externe Abhängigkeit übernehmen oder ihre eigene Implementierung schreiben müssen.

Dieses Paket ersetzt das Paket „System.Numerics.Tensors“.

Weitere Informationen finden Sie im Blogbeitrag zur Ankündigung von .NET 8 RC 2.

Siehe auch