Freigeben über


Neuerungen in der .NET 8-Laufzeit

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

Leistungsverbesserungen

.NET 8 enthält Verbesserungen bei der Codegenerierung und just-in-Time-Kompilierung:.NET 8 includes improvements to code generation and just-in time (JIT) compilation:

  • Verbesserungen der Leistung von Arm64
  • SIMD-Verbesserungen
  • Unterstützung für AVX-512 ISA-Erweiterungen (siehe Vector512 und AVX-512)
  • Cloudeigene Verbesserungen
  • JIT-Durchsatzverbesserungen
  • Schleifen- und allgemeine Optimierungen
  • Optimierter Zugriff für mit ThreadStaticAttribute gekennzeichnete Felder
  • Fortlaufende Registerzuordnung. Arm64 verfügt über zwei Anweisungen für die Tabellenvektorsuche, die erfordern, dass alle Entitäten in ihren Tupelopernden in aufeinander folgenden Registern vorhanden 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 profilgeführte Optimierung (Dynamic Profile Guided Optimization, PGO) verbessert und ist jetzt standardmäßig aktiviert. Sie müssen keine Laufzeitkonfigurationsoption mehr verwenden, um sie zu aktivieren. Dynamic PGO arbeitet eng mit der gestaffelten Kompilierung zusammen, um den Code basierend auf zusätzlicher Instrumentierung, die während Tier 0 implementiert wird, weiter zu optimieren.

Im Durchschnitt erhöht die dynamische PGO die Leistung um etwa 15 %. In einer Benchmark-Suite mit ~4600 Tests verzeichneten 23% Leistungsverbesserungen von 20% oder mehr.

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 Aggregatenbezeichnet) ersetzt die Felder von Strukturvariablen durch Grundtypvariablen, die der JIT dann genauer begründen und optimieren kann.

Der JIT hat diese Optimierung bereits unterstützt, jedoch mit mehreren großen Einschränkungen, darunter:

  • Es wurde nur für Strukturen mit vier oder weniger Feldern unterstützt.
  • Es wurde nur unterstützt, wenn jedes Feld ein primitiver Typ war oder eine einfache Struktur, die einen primitiven Typ umschloss.

Die physische Heraufstufung entfernt diese Einschränkungen, die eine Reihe langjähriger JIT-Probleme behebt.

Müllabfuhr

.NET 8 fügt eine Funktion hinzu, um den Arbeitsspeichergrenzwert automatisch anzupassen. Dies ist in Cloud-Service-Szenarien nützlich, wo die Nachfrage kommt und geht. Aus Kostengründen sollten Dienste den Ressourcenverbrauch je nach Nachfrage hoch- oder herunterskaliert. Wenn ein Dienst eine Abnahme des Bedarfs erkennt, kann er den Ressourcenverbrauch verringern, indem er seine Speichergrenze reduziert. Bisher schlug dies fehl, da der Garbage Collector (GC) diese Änderung nicht erkannte und möglicherweise mehr Speicher als der neue Grenzwert zugewiesen wurde. Mit dieser Änderung können Sie die RefreshMemoryLimit()-API aufrufen, um die GC mit dem neuen Speicherlimit zu aktualisieren.

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

  • Auf 32-Bit-Plattformen (z. B. Windows x86 und Linux ARM) kann .NET keine neue Heap hard limit einrichten, wenn noch keines vorhanden ist.
  • Die API gibt möglicherweise einen Nicht-Null-Statuscode zurück, der angibt, dass die Aktualisierung fehlgeschlagen ist. 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 die aktuelle Speicherauslastung zu verkleinern, und versuchen Sie es dann erneut.
  • Wenn Sie den Speichergrenzwert über die Größe hinaus skalieren, die der GC annimmt, dass der Prozess während des Starts verarbeiten kann, wird der RefreshMemoryLimit Aufruf erfolgreich sein, kann aber nicht mehr Speicher nutzen als das, was er als Grenzwert wahrnimmt.

Der folgende Codeausschnitt zeigt, wie die API aufgerufen wird.

GC.RefreshMemoryLimit();

Sie können auch einige der GC-Konfigurationseinstellungen im Zusammenhang mit dem Speicherlimit aktualisieren. Der folgende Codeausschnitt legt den heap hard limit auf 100 mebibytes (MiB) fest:

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

Die API kann eine InvalidOperationException auslösen, wenn die harte Grenze ungültig ist, z. B. bei negativen Heap hard limit-Prozentsätzen und wenn die harte Grenze 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, bei dem ein einfacheres ICU-Bundle verwendet wird. Im Hybridmodus werden Globalisierungsdaten teilweise aus dem ICU-Bundle abgerufen und teilweise aus Aufrufen in native APIs abgerufen. 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 können und Kulturen verwenden, die von ICU-Daten auf Mobilgeräten gekürzt wurden. Sie können sie auch verwenden, wenn Sie eine kleinere ICU-Datendatei laden möchten. (Die icudt_hybrid.dat Datei ist 34.5 % kleiner als die Standard-ICU-Datendatei icudt.dat.)

Um den Hybrid-Globalisierungsmodus zu verwenden, legen Sie die HybridGlobalization MSBuild-Eigenschaft auf "true" fest:

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

Es gibt 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.

Um zu überprüfen, ob Ihre Anwendung betroffen ist, lesen Sie Verhaltensunterschiede.

Quellgenerierte COM-Interop

.NET 8 enthält einen neuen Quellgenerator, der die Interoperabilität mit COM-Schnittstellen unterstützt. Sie können die 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 ermöglichen. Außerdem wird Code generiert, um das Aufrufen von nicht verwalteten Code in C# zu ermöglichen. Dieser Quellgenerator integriert sich mit LibraryImportAttribute, und Sie können Typen mit GeneratedComInterfaceAttribute als Parameter 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 außerdem das neue GeneratedComClassAttribute-Attribut, mit dem Sie Typen übergeben können, die Schnittstellen mit dem attribut GeneratedComInterfaceAttribute an nicht verwalteten Code implementieren. Der Quellgenerator generiert den Code, der erforderlich ist, um ein COM-Objekt verfügbar zu machen, das die Schnittstellen implementiert und Aufrufe an die verwaltete Implementierung weiterleitet.

Methoden für Schnittstellen mit dem GeneratedComInterfaceAttribute-Attribut unterstützen alle Typen, die auch von LibraryImportAttributeunterstützt werden, und LibraryImportAttribute unterstützt jetzt GeneratedComInterface-attribuierte Typen und GeneratedComClass-attribuierte Typen.

Wenn Ihr C#-Code nur eine GeneratedComInterface-attributierte Schnittstelle verwendet, um entweder ein COM-Objekt aus nicht verwaltetem Code umzuschließen oder ein verwaltetes Objekt aus C# umzuschließen, um nicht verwalteten Code verfügbar zu machen, können Sie die Optionen in der eigenschaft Options 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 zum Erstellen und Verwalten der COM-Objektwrapper und der verwalteten Objektwrapper. Dieser neue Typ kümmert sich um die Bereitstellung der erwarteten .NET-Benutzeroberfläche für COM-Interoperabilität und bietet gleichzeitig Personalisierungsmöglichkeiten für fortgeschrittene Benutzer. Wenn Ihre Anwendung über einen eigenen Mechanismus zum Definieren von Typen aus COM verfügt oder Wenn Sie Szenarien unterstützen müssen, die von der Quelle generiertes COM derzeit nicht unterstützt werden, sollten Sie die Verwendung des neuen StrategyBasedComWrappers Typs in Betracht ziehen, um die fehlenden Features für Ihr Szenario hinzuzufügen und die gleiche .NET-Benutzeroberfläche für Ihre COM-Typen zu erhalten.

Wenn Sie Visual Studio verwenden, machen es neue Analysatoren und Codekorrekturen einfach, Ihren vorhandenen COM-Interopcode in quellgenerierte Interoperabilität zu konvertieren. Neben jeder Schnittstelle, die über „ComImportAttribute“ verfügt, bietet eine Glühbirne die Option zum Konvertieren in aus der Quelle generierter Interop. Durch die Korrektur wird die Schnittstelle so geändert, dass das attribut GeneratedComInterfaceAttribute verwendet wird. Neben jeder Klasse, die eine Schnittstelle mit GeneratedComInterfaceAttributeimplementiert, bietet eine Glühbirne eine Möglichkeit, dem Typ das GeneratedComClassAttribute Attribut hinzuzufügen. Nachdem Ihre Typen konvertiert wurden, können Sie Ihre „DllImport“-Methoden verschieben, um „LibraryImportAttribute“ zu verwenden.

Begrenzungen

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 ist eine Alternative zur bereits vorhandenen spiegelungsbasierten Implementierung.

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 über die bereits vorhandenen spiegelungsbasierten Frameworkimplementierungen aus.

Für die Verwendung des Generators sind keine Quellcodeänderungen erforderlich. Sie ist in AOT-kompilierten Web-Apps standardmäßig aktiviert, und wenn PublishTrimmed auf true (.NET 8+-Apps) festgelegt ist. Bei anderen Projekttypen ist der Quellgenerator standardmäßig deaktiviert, Sie können die EnableConfigurationBindingGenerator Eigenschaft jedoch auf true in der Projektdatei 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; }
    }
}

.NET-Kernbibliotheken

Dieser Abschnitt enthält die folgenden Unterthemen:

Reflexion

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 Funktionszeigermetadaten, einschließlich der aufrufenden Konventionen, des Rückgabetyps und der Parameter.

Anmerkung

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

Die neue Funktionalität wird derzeit nur in der CoreCLR-Laufzeit und MetadataLoadContextimplementiert.

Neue APIs wurden zu System.Typehinzugefügt, z. B. IsFunctionPointer, sowie zu System.Reflection.PropertyInfo, System.Reflection.FieldInfound System.Reflection.ParameterInfo. Der folgende Code zeigt, wie Sie einige der neuen APIs für die Spiegelung verwenden.

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 die 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 viele Verbesserungen an der System.Text.Json Serialisierungs- und Deserialisierungsfunktionalität in .NET 8 vorgenommen. Sie können z. B. die Behandlung von JSON-Eigenschaften anpassen, die sich nicht im POCO 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 verfügt über integrierte Unterstützung für die folgenden zusätzlichen Typen.

  • 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]
    

Quellgenerator

.NET 8 enthält Verbesserungen des System.Text.Json-Quellgenerators, die darauf abzielen, das Native AOT--Erlebnis vergleichbar mit dem spiegelungsbasierten Serialisiererzu gestalten. Zum Beispiel:

  • Der Quellgenerator unterstützt jetzt die Serialisierung von Typen mit required- und init Eigenschaften. 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 Diagnose (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 Supertyp, mit dem der Wert serialisiert werden soll.

  • Neuer Konvertertyp JsonStringEnumConverter<TEnum>. Die vorhandene JsonStringEnumConverter Klasse wird in Native AOT nicht unterstützt. Sie können Ihre Enum-Typen wie folgt annotieren:

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

    Weitere Informationen finden Sie unter Enum-Felder als Zeichenfolgen serialisieren.

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

    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.

Kettenquellgeneratoren

Die JsonSerializerOptions Klasse enthält eine neue TypeInfoResolverChain-Eigenschaft, die die vorhandene TypeInfoResolver-Eigenschaft ergänzt. Diese Eigenschaften werden in der Vertragsanpassung für verkettete Quellgeneratoren verwendet. Das Hinzufügen der neuen Eigenschaft bedeutet, dass Sie nicht alle verketteten Komponenten an einer Aufrufstelle angeben müssen – sie können nachträglich hinzugefügt werden. TypeInfoResolverChain ermöglicht es Ihnen auch, die Kette zu untersuchen oder Komponenten daraus zu entfernen. Weitere Informationen finden Sie unter Kombinieren von Quellgeneratoren.

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

Schnittstellenhierarchien

.NET 8 bietet Unterstützung für die Serialisierung von Eigenschaften aus Schnittstellenhierarchien.

Der folgende Code zeigt ein Beispiel, in dem die Eigenschaften sowohl von der sofort implementierten Schnittstelle als auch deren 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-Eigenschaftsnamen (mit Bindestrich). 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 sich global für diese Unterstützung zu entscheiden, setzen Sie die neue Option PreferredObjectCreationHandlingauf JsonObjectCreationHandling.Populate. Wenn die Kompatibilität ein Problem darstellt, können Sie die Funktionalität auch präziser aktivieren, indem Sie das attribut [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] auf bestimmte Typen setzen, deren Eigenschaften aufgefüllt werden sollen, oder auf einzelnen 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 Names und Company Eigenschaften behalten 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.

Spiegelungsbasierte Standardeinstellung deaktivieren

Sie können jetzt die Verwendung des spiegelbasierten Serialisierungsprogramms standardmäßig 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. Um die standardmäßige spiegelungsbasierte Serialisierung zu deaktivieren, indem sie erfordert, dass ein JsonSerializerOptions-Argument an die JsonSerializer Serialisierungs- und Deserialisierungsmethoden übergeben wird, legen Sie die JsonSerializerIsReflectionEnabledByDefault MSBuild-Eigenschaft auf false in der Projektdatei fest.

Verwenden Sie die neue IsReflectionEnabledByDefault-API, um den Wert des Featureswitches 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 Deaktivieren von Spiegelungsstandardeinstellungen.

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ückgeben, z. B. 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);

WithAddedModifier-Erweiterungsmethode

Mit der neuen WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) Erweiterungsmethode 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-Überladungen

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

Einfrieren einer JsonSerializerOptions-Instanz

Mit den folgenden neuen Methoden können Sie kontrollieren, wann eine JsonSerializerOptions-Instanz eingefroren 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 RequiresUnreferenceCode/RequiresDynamicCode gekennzeichnet und eignet sich daher nicht für native AOT-Anwendungen.

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

Zeitabstraktion

Die neue TimeProvider Klasse und die ITimer Schnittstelle fügen Zeitabstraktion Funktionalität hinzu, mit der Sie Zeit in Testszenarien simulieren können. Darüber hinaus können Sie die Zeitstraktion verwenden, um Task Vorgänge zu modellieren, die sich auf die Zeitentwicklung mit Task.Delay und Task.WaitAsyncverlassen. Die Zeitstraktion unterstützt die folgenden wesentlichen Zeitvorgänge:

  • Abrufen der lokalen und UTC-Zeit
  • Abrufen eines Zeitstempels zum Messen der Leistung
  • Erstellen eines Zeitgebers

Der folgende Codeausschnitt zeigt einige Verwendungsbeispiele.

// 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-Verbesserungen

Wenn Sie die Ausgabe einer zeichenfolgenähnlichen Darstellung Ihres Typs in einen Zielbereich aktivieren möchten, implementieren Sie die neue IUtf8SpanFormattable-Schnittstelle auf Ihrem Typ. Diese neue Schnittstelle ist eng mit ISpanFormattableverknüpft, zielt jedoch auf UTF8 und Span<byte> anstelle von UTF16 und Span<char>.

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 von 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, UIntPtrund Versionformatieren können.

Neue Utf8.TryWrite Methoden bieten ein UTF8-basiertes Gegenstück zu den vorhandenen MemoryExtensions.TryWrite Methoden, die UTF16-basiert sind. Sie können eine interpolierte Zeichenfolgensyntax verwenden, um einen komplexen Ausdruck direkt in eine Spanne 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.

Die Implementierung verwendet auch die neue Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32)-Methode, die zusammen mit ihrem Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32) Gegenstück die Codierung und Decodierung in eine Zielspanne unterstützt. Wenn die Spanne nicht lang genug ist, um den resultierenden Zustand zu halten, geben die Methoden false zurück, anstatt eine Ausnahme auszuwerfen.

Methoden für das Arbeiten mit Zufallszahlen

Die System.Random- und System.Security.Cryptography.RandomNumberGenerator Typen führen zwei neue Methoden für die Arbeit mit Zufallszahlen ein.

GetItems<T>()

Mit den neuen methoden System.Random.GetItems und System.Security.Cryptography.RandomNumberGenerator.GetItems können Sie zufällig eine bestimmte Anzahl von Elementen aus einem Eingabesatz auswählen. Das folgende Beispiel zeigt, wie Sie System.Random.GetItems<T>() (auf der instanz, die von der Random.Shared-Eigenschaft bereitgestellt wird) verwenden, um zufällig 31 Elemente in ein Array einzufügen. Dieses Beispiel könnte in einem Spiel von "Simon" verwendet werden, bei dem spieler eine Sequenz von farbigen Schaltflächen 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 Random.Shuffle- und RandomNumberGenerator.Shuffle<T>(Span<T>)-Methoden können Sie die Reihenfolge einer Spanne zufällig skalieren. Diese Methoden sind nützlich, um Trainingsverzerrungen im maschinellen Lernen zu reduzieren (damit nicht immer zuerst das Training und zuletzt immer der Test erfolgt).

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 ein, die auf die Verbesserung der App-Leistung abzielen.

  • Der neue System.Collections.Frozen-Namespace enthält die Sammlungstypen FrozenDictionary<TKey,TValue> und FrozenSet<T>. Diese Typen lassen keine Änderungen an Schlüsseln und Werten zu, nachdem eine Auflistung erstellt wurde. 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 beibehalten werden, z. B.:

    private static readonly FrozenDictionary<string, bool> s_configurationData =
        LoadConfigurationData().ToFrozenDictionary();
    
    // ...
    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 Kompilierungszeit 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 XXH3- und XXH128-Hashalgorithmen.

System.Numerics und System.Runtime.Intrinsics

In diesem Abschnitt werden Verbesserungen an den namespaces System.Numerics und System.Runtime.Intrinsics behandelt.

  • Vector256<T>, Matrix3x2und 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.
  • Hardwareinterne Features werden jetzt mit dem attribut ConstExpected kommentiert. Dadurch wird sichergestellt, dass Benutzer wissen, wann die zugrunde liegende Hardware eine Konstante erwartet und wann ein nicht konstanter Wert unerwartet die Leistung beeinträchtigen kann.
  • Die Lerp(TSelf, TSelf, TSelf)Lerp-API wurde zu IFloatingPointIeee754<TSelf> und daher zu float (Single), double (Double) und Halfhinzugefügt. Diese API ermöglicht eine lineare Interpolation zwischen zwei Werten, die effizient und korrekt ausgeführt werden können.

Vector512 und AVX-512

.NET Core 3.0 erweiterte SIMD-Unterstützung, um die plattformspezifischen hardwarespezifischen systeminternen APIs für x86/x64 einzuschließen. .NET 5 hat Unterstützung für Arm64 hinzugefügt und .NET 7 hat die plattformübergreifenden Hardware-Eigenschaften 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 .NET 8 enthält Unterstützung für die folgenden wichtigen Features von AVX-512:

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

Wenn Sie über Hardware verfügen, die die Funktionalität unterstützt, meldet Vector512.IsHardwareAccelerated jetzt true.

.NET 8 fügt auch mehrere plattformspezifische Klassen unter dem System.Runtime.Intrinsics.X86 Namespace hinzu:

Diese Klassen folgen der gleichen allgemeinen Form wie andere Anweisungssatzarchitekturen (ISAs), da sie 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.

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

Datenüberprüfung

Der System.ComponentModel.DataAnnotations-Namespace enthält neue Datenüberprüfungsattribute, die für Validierungsszenarien in cloudeigenen Diensten vorgesehen sind. Während die bereits vorhandenen DataAnnotations-Validatoren auf die typische Überprüfung der Benutzereingaben in der Benutzeroberfläche ausgerichtet sind, z. B. Felder in einem Formular, sind die neuen Attribute so konzipiert, dass sie Daten validieren, die nicht vom Benutzer eingegeben werden, wie z. B. -Konfigurationsoptionen. Zusätzlich zu den neuen Attributen wurden dem RangeAttribute Typ 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 Schlüssel-Wert-Paartags an Meter und Instrument Objekte anfügen, wenn Sie sie erstellen. Aggregatoren veröffentlichter Metrikmessungen können die Tags verwenden, um die aggregierten Werte zu 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);

Die neuen APIs umfassen:

Kryptographie

.NET 8 fügt Unterstützung für die SHA-3-Hashinggrundtypen hinzu. (SHA-3 wird derzeit von Linux mit OpenSSL 1.1.1 oder höher und Windows 11 Build 25324 oder höher unterstützt.) APIs, bei denen SHA-2 verfügbar ist, bieten jetzt ein SHA-3-Kompliment an. Dazu gehören 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, wo der Algorithmus konfigurierbar ist; und RSAEncryptionPadding.OaepSHA3_256, RSAEncryptionPadding.OaepSHA3_384und RSAEncryptionPadding.OaepSHA3_512 für DIE RSA OAEP-Verschlüsselung.

Das folgende Beispiel zeigt, wie Sie die APIs verwenden, einschließlich der SHA3_256.IsSupported-Eigenschaft, um festzustellen, 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
{
    // ...
}

Die SHA-3-Unterstützung zielt derzeit auf die Unterstützung kryptografischer Grundtypen ab. Von übergeordneten Konstrukten und Protokollen wird zunächst nicht erwartet, dass sie SHA-3 vollständig unterstützen. Zu diesen Protokollen gehören X.509-Zertifikate, SignedXmlund COSE.

Vernetzung

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-, wodurch ein verschlüsselter Kanal zwischen dem Client und dem Proxy erstellt wird, damit alle Anforderungen mit vollständiger Privatsphäre verarbeitet werden können.

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

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, die es Ihnen ermöglichen, alle in einem Verzeichnis enthaltenen Dateien zu sammeln, sie zu zippen und dann die resultierende ZIP-Datei im bereitgestellten Datenstrom zu speichern. 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 speicherplatzschränkt ist, da sie vermeiden, den Datenträger als Zwischenschritt zu verwenden.

Erweiterungsbibliotheken

Dieser Abschnitt enthält die folgenden Unterthemen:

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, wie Sie Dienste registrieren und nutzen. Dies sind einige der neuen APIs:

Das folgende Beispiel zeigt, wie Sie schlüsselierte DI-Dienste 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 verfügen jetzt über weitere Optionen für die Ausführung während des Anwendungslebenszyklus. IHostedService bereitgestellte StartAsync und StopAsync, und jetzt bietet IHostedLifecycleService die folgenden zusätzlichen Methoden:

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

Das folgende Beispiel zeigt, wie die neuen APIs verwendet werden.

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.

Optionsüberprüfung

Quellgenerator

Um den Startaufwand zu reduzieren und den Überprüfungsfeaturesatz zu verbessern, haben wir einen Quellcodegenerator eingeführt, der die Validierungslogik implementiert. Der folgende Code zeigt Beispielmodelle und Validatorklassen.

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 App Abhängigkeitsinjektion verwendet, können Sie die Überprüfung 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

.NET 8 führt den ValidateOptionsResultBuilder Typ ein, um die Erstellung eines ValidateOptionsResult-Objekts zu erleichtern. Wichtig ist, dass dieser Generator die Anhäufung mehrerer Fehler ermöglicht. Zuvor war das Erstellen des ValidateOptionsResult Objekts, das zum Implementieren IValidateOptions<TOptions>.Validate(String, TOptions) erforderlich ist, schwierig und führte manchmal zu Mehrschichtüberprüfungsfehlern. Wenn mehrere Fehler aufgetreten sind, wird der Überprüfungsprozess häufig beim ersten Fehler beendet.

Der folgende Codeausschnitt zeigt ein Beispiel für die 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. Zuvor mussten Sie entweder den parameterlosen Konstruktor oder den Konstruktor auswählen, der alle Parameter (Ereignis-ID, Protokollebene und Nachricht) benötigt hat. Die neuen Überladungen bieten eine größere Flexibilität beim Angeben der erforderlichen Parameter bei reduziertem Codeaufwand. 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 Abhängigkeitseinfügungscontainern (DI) registrieren und verwenden, um Meter Objekte isoliert zu erstellen.

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

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

Verbraucher können dann die Meter-Factory abrufen und verwenden, um ein neues Meter-Objekt zu 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 zusammen mit Zeitstempeln aufzeichnen. Darüber hinaus bietet die Klasse die Flexibilität, einen Zeitanbieter Ihrer Wahl für die genaue Zeitstempelgenerierung zu verwenden.

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 System.Numerics.Tensors.TensorPrimitives Typ, der Unterstützung für Tensorvorgänge hinzugibt. Die Tensorgrundtypen optimieren datenintensive Workloads wie KI und maschinelles Lernen.

KI-Anwendungen wie die semantische Suche und die durch Abruf erweiterte Generierung (RAG) erweitern die Fähigkeiten zur Verarbeitung natürlicher Sprache großer Sprachmodelle, wie ChatGPT, indem sie Eingabeaufforderungen mit relevanten Daten ergänzen. Für diese Workloads sind Vorgänge auf Vektoren wie Kosinusähnlichkeit, um die relevantesten Daten zu finden, um eine Frage zu beantworten, von entscheidender Bedeutung. Der TensorPrimitives Typ stellt APIs für Vektorvorgänge bereit.

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

Unterstützung von nativem AOT

Die Option zum Veröffentlichen als Native AOT wurde erstmals in .NET 7 eingeführt. Durch das Veröffentlichen einer App mit nativem AOT wird eine vollständig eigenständige Version Ihrer App erstellt, die keine Laufzeit benötigt – alles ist in einer einzelnen Datei enthalten. .NET 8 bietet die folgenden Verbesserungen bei der nativen AOT-Publikation:

  • Fügt Unterstützung für die x64- und Arm64-Architekturen auf macOS-hinzu.

  • Reduziert die Größen von Native AOT-Apps unter Linux bis zu 50 %. Die folgende Tabelle zeigt die Größe einer mit Native AOT veröffentlichten "Hello World"-App, die die gesamte .NET-Laufzeit auf .NET 7 vs. .NET 8 enthält:

    Betriebssystem .NET 7 .NET 8
    Linux x64 (mit -p:StripSymbols=true) 3,76 MB 1,84 MB
    Windows x64 2,85 MB 1,77 MB
  • Hiermit können Sie eine Optimierungseinstellung angeben: Größe oder Geschwindigkeit. Standardmäßig wählt der Compiler die Generierung von schnellem Code aus, wobei die Größe der Anwendung beachtet wird. Sie können jedoch die <OptimizationPreference> MSBuild-Eigenschaft verwenden, um speziell für eine oder die andere zu optimieren. Weitere Informationen finden Sie unter Optimieren von AOT-Bereitstellungen.

Auf iOS-ähnliche Plattformen mit nativem AOT abzielen

.NET 8 startet die Arbeit, um native AOT-Unterstützung für iOS-ähnliche Plattformen zu aktivieren. Sie können jetzt .NET iOS- und .NET MAUI-Anwendungen mit Native AOT auf den folgenden Plattformen erstellen und ausführen:

  • ios
  • iossimulator
  • maccatalyst
  • tvos
  • tvossimulator

Vorläufige Tests zeigen, dass die App-Größe auf dem Datenträger um etwa 35% für .NET iOS-Apps verringert wird, die native AOT anstelle von Mono verwenden. Die App-Größe auf dem Datenträger für .NET MAUI iOS-Apps nimmt um bis zu 50 % ab. Darüber hinaus ist die Startzeit ebenfalls schneller. .NET iOS-Apps haben etwa 28% schnellere Startzeit, während .NET MAUI iOS-Apps im Vergleich zu Mono etwa 50% bessere Startleistung aufweisen. Die .NET 8-Unterstützung ist experimentell und nur der erste Schritt für das Feature insgesamt. Weitere Informationen finden Sie im Blogbeitrag .NET 8 Performance Improvements in .NET MAUI.

Native AOT-Unterstützung ist als Opt-In-Feature für die App-Bereitstellung verfügbar; Mono ist weiterhin die Standardlaufzeit für die App-Entwicklung und -Bereitstellung. Um eine .NET MAUI-Anwendung mit nativem AOT auf einem iOS-Gerät zu erstellen und auszuführen, verwenden Sie dotnet workload install maui zum Installieren der .NET MAUI-Workload und dotnet new maui -n HelloMaui zum Erstellen der App. Legen Sie dann die MSBuild-Eigenschaft PublishAot auf true in der Projektdatei fest.

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

Wenn Sie die erforderliche Eigenschaft festlegen und dotnet publish ausführen, wie im folgenden Beispiel gezeigt, wird die App mithilfe von Native AOT bereitgestellt.

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

Begrenzungen

Nicht alle iOS-Features sind mit Native AOT kompatibel. Ebenso sind nicht alle in iOS häufig verwendeten Bibliotheken mit NativeAOT kompatibel. Zusätzlich zu den bestehenden Einschränkungen der nativen AOT-Bereitstellungzeigt die folgende Liste einige der anderen Einschränkungen bei der Ausrichtung auf iOS-ähnliche Plattformen:

  • Die Verwendung von Native AOT ist nur während der App-Bereitstellung (dotnet publish) aktiviert.
  • Verwaltetes Codedebugging wird nur mit Mono unterstützt.
  • Die Kompatibilität mit .NET MAUI Framework ist eingeschränkt.

AOT-Kompilierung für Android-Apps

Um die App-Größe zu verringern, verwenden .NET- und .NET MAUI-Apps für Android den profilierten Kompilierungsmodus (Ahead-of-Time, AOT), wenn sie im Releasemodus integriert sind. Die profilierte AOT-Kompilierung wirkt sich auf weniger Methoden als die normale AOT-Kompilierung aus. .NET 8 führt die <AndroidStripILAfterAOT>-Eigenschaft ein, mit der Sie sich für weitere AOT-Kompilierung für Android-Apps anmelden können, um die App-Größe noch weiter zu verringern.

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

Standardmäßig überschreibt die Einstellung von AndroidStripILAfterAOT auf true die Standardeinstellung AndroidEnableProfiledAot, sodass (fast) alle Methoden, die AOT-kompiliert wurden, gekürzt werden können. Sie können auch profiliertes AOT- und IL-Stripping zusammen verwenden, indem Sie beide Eigenschaften explizit auf truefestlegen:

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

Plattformübergreifend erstellte Windows-Apps

Wenn Sie Apps für Windows auf Nicht-Windows-Plattformen erstellen, wird die resultierende ausführbare Datei jetzt mit allen angegebenen Win32-Ressourcen aktualisiert, z. B. Anwendungssymbol, Manifest, Versionsinformationen.

Bisher mussten Anwendungen unter Windows erstellt werden, um über solche Ressourcen zu verfügen. Das Beheben dieser Lücke bei der bauübergreifenden Unterstützung war eine beliebte Anforderung, da es sich um einen erheblichen Schmerzpunkt handelte, der sowohl die Komplexität der Infrastruktur als auch die Ressourcennutzung beeinflusste.

Siehe auch