Share via


Nyheter i .NET 8-körningen

I den här artikeln beskrivs nya funktioner i .NET-körningen för .NET 8.

Prestandaförbättringar

.NET 8 innehåller förbättringar av jit-kompilering (kodgenerering och just-in-time):

  • Prestandaförbättringar för Arm64
  • SIMD-förbättringar
  • Stöd för AVX-512 ISA-tillägg (se Vector512 och AVX-512)
  • Molnbaserade förbättringar
  • Förbättringar av JIT-dataflöde
  • Loop och allmänna optimeringar
  • Optimerad åtkomst för fält som markerats med ThreadStaticAttribute
  • På varandra följande registerallokering. Arm64 har två instruktioner för tabellvektorsökning, som kräver att alla entiteter i deras tupplar finns i efterföljande register.
  • JIT/NativeAOT kan nu avregistrera och automatiskt vektorisera vissa minnesåtgärder med SIMD, till exempel jämförelse, kopiering och nollning, om det kan fastställa deras storlekar vid kompileringstiden.

Dessutom har dynamisk profilstyrd optimering (PGO) förbättrats och är nu aktiverad som standard. Du behöver inte längre använda ett körningskonfigurationsalternativ för att aktivera det. Dynamisk PGO fungerar hand i hand med nivåindelad kompilering för att ytterligare optimera kod baserat på ytterligare instrumentation som införs under nivå 0.

I genomsnitt ökar dynamisk PGO prestanda med cirka 15 %. I en benchmark-svit med ~4600-tester såg 23 % prestandaförbättringar på 20 % eller mer.

Codegen struct-befordran

.NET 8 innehåller ett nytt optimeringspass för fysisk befordran för codegen som generaliserar JIT:ns möjlighet att höja upp structvariabler. Den här optimeringen (kallas även skalär ersättning av aggregeringar) ersätter fälten för structvariabler med primitiva variabler som JIT sedan kan resonera om och optimera mer exakt.

JIT har redan stöd för den här optimeringen, men med flera stora begränsningar, inklusive:

  • Det stöds bara för structs med fyra eller färre fält.
  • Det stöds bara om varje fält var en primitiv typ, eller en enkel struct som omsluter en primitiv typ.

Fysisk befordran tar bort dessa begränsningar, vilket åtgärdar ett antal långvariga JIT-problem.

Skräpinsamling

.NET 8 lägger till en funktion för att justera minnesgränsen i farten. Detta är användbart i molntjänstscenarier, där efterfrågan kommer och går. För att vara kostnadseffektiva bör tjänsterna skalas upp och ned på resursförbrukningen när efterfrågan varierar. När en tjänst upptäcker en minskning av efterfrågan kan den skala ned resursförbrukningen genom att minska minnesgränsen. Tidigare skulle detta misslyckas eftersom skräpinsamlaren (GC) inte kände till ändringen och kan allokera mer minne än den nya gränsen. Med den här ändringen kan du anropa API:et RefreshMemoryLimit() för att uppdatera GC med den nya minnesgränsen.

Det finns vissa begränsningar att vara medveten om:

  • På 32-bitarsplattformar (till exempel Windows x86 och Linux ARM) kan .NET inte upprätta en ny heap-hård gräns om det inte redan finns en.
  • API:et kan returnera en statuskod som inte är noll som anger att uppdateringen misslyckades. Detta kan inträffa om nedskalningen är för aggressiv och inte lämnar något utrymme för GC att manövrera. I det här fallet bör du överväga att anropa GC.Collect(2, GCCollectionMode.Aggressive) för att minska den aktuella minnesanvändningen och sedan försöka igen.
  • Om du skalar upp minnesgränsen utöver den storlek som GC tror att processen kan hantera under starten kommer anropet RefreshMemoryLimit att lyckas, men det kommer inte att kunna använda mer minne än vad det uppfattar som gränsen.

Följande kodfragment visar hur du anropar API:et.

GC.RefreshMemoryLimit();

Du kan också uppdatera några av GC-konfigurationsinställningarna som är relaterade till minnesgränsen. Följande kodfragment anger den hårda heapgränsen till 100 mebibyte (MiB):

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

API:et kan utlösa en InvalidOperationException om den hårda gränsen är ogiltig, till exempel vid negativa procentsatser för heaphård gräns och om den hårda gränsen är för låg. Detta kan inträffa om den hårda heap-gränsen som uppdateringen kommer att ange, antingen på grund av nya AppData-inställningar eller underförstått av ändringar i containerminnesgränsen, är lägre än vad som redan har checkats in.

Globalisering för mobilappar

Mobilappar i iOS, tvOS och MacCatalyst kan välja ett nytt hybridglobaliseringsläge som använder ett lättare ICU-paket. I hybridläge hämtas globaliseringsdata delvis från ICU-paketet och delvis från anrop till interna API:er. Hybridläget hanterar alla nationella inställningar som stöds av mobilen.

Hybridläget är mest lämpligt för appar som inte kan fungera i invariant globaliseringsläge och som använder kulturer som har trimmats från ICU-data på mobilen. Du kan också använda den när du vill läsa in en mindre ICU-datafil. (Filen icudt_hybrid.dat är 34,5 % mindre än standard-ICU-datafilen icudt.dat.)

Om du vill använda hybridglobaliseringsläget anger du HybridGlobalization egenskapen MSBuild till true:

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

Det finns vissa begränsningar att vara medveten om:

  • På grund av begränsningar i det interna API:et stöds inte alla globaliserings-API:er i hybridläge.
  • Vissa API:er som stöds har olika beteende.

Information om hur du kontrollerar om ditt program påverkas finns i Beteendeskillnader.

Källgenererad COM-interop

.NET 8 innehåller en ny källgenerator som stöder samverkan med COM-gränssnitt. Du kan använda GeneratedComInterfaceAttribute för att markera ett gränssnitt som ett COM-gränssnitt för källgeneratorn. Källgeneratorn genererar sedan kod för att aktivera anrop från C#-kod till ohanterad kod. Den genererar också kod för att aktivera anrop från ohanterad kod till C#. Den här källgeneratorn integreras med LibraryImportAttribute, och du kan använda typer med GeneratedComInterfaceAttribute som parametrar och returnera typer i LibraryImport-attributmetoder.

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

Källgeneratorn stöder också det nya GeneratedComClassAttribute attributet så att du kan skicka typer som implementerar gränssnitt med GeneratedComInterfaceAttribute attributet till ohanterad kod. Källgeneratorn genererar den kod som krävs för att exponera ett COM-objekt som implementerar gränssnitten och vidarebefordrar anrop till den hanterade implementeringen.

Metoder för gränssnitt med GeneratedComInterfaceAttribute attributet stöder alla samma typer som LibraryImportAttribute, och LibraryImportAttribute stöder GeneratedComInterfacenu -attributtyper och GeneratedComClass-attributtyper.

Om C#-koden bara använder ett GeneratedComInterface-attributgränssnitt för att antingen omsluta ett COM-objekt från ohanterad kod eller omsluta ett hanterat objekt från C# för att exponera för ohanterad kod, kan du använda alternativen i Options egenskapen för att anpassa vilken kod som ska genereras. De här alternativen innebär att du inte behöver skriva marshallers för scenarier som du vet inte kommer att användas.

Källgeneratorn använder den nya StrategyBasedComWrappers typen för att skapa och hantera COM-objektomslutningarna och de hanterade objektomslutningarna. Den här nya typen hanterar den förväntade .NET-användarupplevelsen för COM-interop och tillhandahåller anpassningspunkter för avancerade användare. Om ditt program har en egen mekanism för att definiera typer från COM eller om du behöver stödja scenarier som källgenererad COM för närvarande inte stöder kan du överväga att använda den nya StrategyBasedComWrappers typen för att lägga till de funktioner som saknas för ditt scenario och få samma .NET-användarupplevelse för dina COM-typer.

Om du använder Visual Studio gör nya analysverktyg och kodkorrigeringar det enkelt att konvertera din befintliga COM-interop-kod till att använda källgenererad interop. Bredvid varje gränssnitt som har ComImportAttribute, erbjuder en glödlampa ett alternativ för att konvertera till källgenererad interop. Korrigeringen ändrar gränssnittet så att det använder attributet GeneratedComInterfaceAttribute . Bredvid varje klass som implementerar ett gränssnitt med GeneratedComInterfaceAttribute, erbjuder en glödlampa ett alternativ för att lägga GeneratedComClassAttribute till attributet till typen. När dina typer har konverterats kan du flytta dina DllImport metoder för att använda LibraryImportAttribute.

Begränsningar

COM-källgeneratorn stöder inte lägenhetstillhörighet med hjälp av nyckelordet new för att aktivera en COM CoClass och följande API:er:

Generator för konfigurationsbindningskälla

.NET 8 introducerar en källgenerator för att tillhandahålla AOT och trimvänlig konfiguration i ASP.NET Core. Generatorn är ett alternativ till den befintliga reflektionsbaserade implementeringen.

Källgeneratoravsökningarna för Configure(TOptions), Bindoch Get anrop för att hämta typinformation från. När generatorn är aktiverad i ett projekt väljer kompilatorn implicit genererade metoder framför de befintliga reflektionsbaserade ramverksimplementeringarna.

Inga källkodsändringar krävs för att använda generatorn. Det är aktiverat som standard i AOT'd-webbappar. För andra projekttyper är källgeneratorn inaktiverad som standard, men du kan välja genom att ange EnableConfigurationBindingGenerator egenskapen till true i projektfilen:

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

Följande kod visar ett exempel på hur du anropar pärmen.

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

Det här avsnittet innehåller följande underavsnitt:

Reflektion

Funktionspekare introducerades i .NET 5, men motsvarande stöd för reflektion lades inte till vid den tidpunkten. När du använder typeof eller reflekterar en funktionspekare, till exempel typeof(delegate*<void>()) eller FieldInfo.FieldType respektive, returnerades en IntPtr . Från och med .NET 8 returneras ett System.Type objekt i stället. Den här typen ger åtkomst till metadata för funktionspekare, inklusive anropskonventioner, returtyp och parametrar.

Kommentar

En funktionspekarinstans, som är en fysisk adress till en funktion, fortsätter att representeras som en IntPtr. Endast reflektionstypen har ändrats.

Den nya funktionen implementeras för närvarande endast i CoreCLR-körningen och MetadataLoadContext.

Nya API:er har lagts till i , till System.Typeexempel IsFunctionPointer, och till System.Reflection.PropertyInfo, System.Reflection.FieldInfooch System.Reflection.ParameterInfo. Följande kod visar hur du använder några av de nya API:erna för reflektion.

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

I föregående exempel genereras följande utdata:

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

Serialization

Många förbättringar har gjorts för System.Text.Json serialiserings- och deserialiseringsfunktioner i .NET 8. Du kan till exempel anpassa hanteringen av medlemmar som inte finns i JSON-nyttolasten.

I följande avsnitt beskrivs andra serialiseringsförbättringar:

Mer information om JSON-serialisering i allmänhet finns i JSON-serialisering och deserialisering i .NET.

Inbyggt stöd för ytterligare typer

Serialiseraren har inbyggt stöd för följande ytterligare typer.

  • Half, Int128och UInt128 numeriska typer.

    Console.WriteLine(JsonSerializer.Serialize(
        [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ]
    ));
    // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
    
  • Memory<T> och ReadOnlyMemory<T> värden. byte värden serialiseras till Base64-strängar och andra typer till JSON-matriser.

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

Källgenerator

.NET 8 innehåller förbättringar av System.Text.Json-källgeneratorn som syftar till att göra den interna AOT-upplevelsen i nivå med den reflektionsbaserade serialiseraren. Till exempel:

  • Källgeneratorn stöder nu serialiseringstyper med required och init egenskaper. Båda dessa stöds redan i reflektionsbaserad serialisering.

  • Förbättrad formatering av källgenererad kod.

  • JsonSourceGenerationOptionsAttribute funktionsparitet med JsonSerializerOptions. Mer information finns i Ange alternativ (källgenerering).

  • Ytterligare diagnostik (till exempel SYSLIB1034 och SYSLIB1039).

  • Ta inte med typer av ignorerade eller otillgängliga egenskaper.

  • Stöd för kapslingsdeklarationer JsonSerializerContext inom godtyckliga typer.

  • Stöd för kompilatorgenererade eller outsägliga typer i svagt typade källgenereringsscenarier. Eftersom kompilatorgenererade typer inte uttryckligen kan anges av källgeneratorn utför System.Text.Json nu den närmast överordnade lösningen vid körning. Den här lösningen avgör den lämpligaste supertypen som värdet ska serialiseras med.

  • Ny konverterartyp JsonStringEnumConverter<TEnum>. Den befintliga JsonStringEnumConverter klassen stöds inte i intern AOT. Du kan kommentera dina uppräkningstyper på följande sätt:

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

    Mer information finns i Serialisera uppräkningsfält som strängar.

  • Med den nya JsonConverter.Type egenskapen kan du söka efter typen av en icke-generisk JsonConverter instans:

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

    Egenskapen är null eftersom den returnerar null för JsonConverterFactory instanser och typeof(T) för JsonConverter<T> instanser.

Kedjekällgeneratorer

Klassen JsonSerializerOptions innehåller en ny TypeInfoResolverChain egenskap som kompletterar den befintliga TypeInfoResolver egenskapen. Dessa egenskaper används i kontraktsanpassning för länkning av källgeneratorer. Tillägget av den nya egenskapen innebär att du inte behöver ange alla länkade komponenter på en anropsplats – de kan läggas till i efterhand. TypeInfoResolverChain låter dig också introspekta kedjan eller ta bort komponenter från den. Mer information finns i Kombinera källgeneratorer.

Dessutom JsonSerializerOptions.AddContext<TContext>() är nu föråldrad. Det har ersatts av TypeInfoResolver egenskaperna och TypeInfoResolverChain . Mer information finns i SYSLIB0049.

Gränssnittshierarkier

.NET 8 lägger till stöd för serialisering av egenskaper från gränssnittshierarkier.

Följande kod visar ett exempel där egenskaperna från både det omedelbart implementerade gränssnittet och dess basgränssnitt serialiseras.

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

Namngivningsprinciper

JsonNamingPolicy innehåller nya namngivningsprinciper för snake_case (med understreck) och kebab-case (med bindestreck) egenskapsnamnkonverteringar. Använd dessa principer på samma sätt som den befintliga JsonNamingPolicy.CamelCase principen:

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

Mer information finns i Använda en inbyggd namngivningsprincip.

Skrivskyddade egenskaper

Nu kan du deserialisera till skrivskyddade fält eller egenskaper (det vill: de som inte har någon set accessor).

Om du vill välja det här stödet globalt anger du ett nytt alternativ, PreferredObjectCreationHandling, till JsonObjectCreationHandling.Populate. Om kompatibilitet är ett problem kan du också aktivera funktionen mer detaljerat genom att placera [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] attributet på specifika typer vars egenskaper ska fyllas i eller på enskilda egenskaper.

Tänk till exempel på följande kod som deserialiserar till en CustomerInfo typ som har två skrivskyddade egenskaper.

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

Före .NET 8 ignorerades indatavärdena och Names egenskaperna och Company behöll sina standardvärden.

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

Nu används indatavärdena för att fylla i de skrivskyddade egenskaperna under deserialiseringen.

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

Mer information om beteendet för att fylla i deserialisering finns i Fylla i initierade egenskaper.

Inaktivera reflektionsbaserad standard

Nu kan du inaktivera med hjälp av den reflektionsbaserade serialiseraren som standard. Den här inaktiveringen är användbar för att undvika oavsiktlig rotning av reflektionskomponenter som inte ens används, särskilt i trimmade och interna AOT-appar. Om du vill inaktivera standardreflektionsbaserad serialisering genom att kräva att ett JsonSerializerOptions argument skickas till JsonSerializer serialiserings- och deserialiseringsmetoderna JsonSerializerIsReflectionEnabledByDefault anger du egenskapen MSBuild till false i projektfilen.

Använd det nya IsReflectionEnabledByDefault API:et för att kontrollera värdet för funktionsväxeln. Om du är en biblioteksförfattare som bygger ovanpå System.Text.Jsonkan du förlita dig på egenskapen för att konfigurera dina standardvärden utan att oavsiktligt rota reflektionskomponenter.

Mer information finns i Inaktivera reflektionsstandarder.

Nya JsonNode API-metoder

Typerna JsonNode och System.Text.Json.Nodes.JsonArray innehåller följande nya metoder.

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

Icke-offentliga medlemmar

Du kan välja icke-offentliga medlemmar i serialiseringskontraktet för en viss typ med hjälp av JsonIncludeAttribute och JsonConstructorAttribute attributanteckningar.

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

Mer information finns i Använda oföränderliga typer och icke-offentliga medlemmar och accessorer.

API:er för strömmande deserialisering

.NET 8 innehåller nya IAsyncEnumerable<T> metoder för deserialisering av strömmande tillägg, till exempel GetFromJsonAsAsyncEnumerable. Liknande metoder har funnits som returnerar Task<TResult>, till exempel HttpClientJsonExtensions.GetFromJsonAsync. De nya tilläggsmetoderna anropar API:er för direktuppspelning och returnerar IAsyncEnumerable<T>.

Följande kod visar hur du kan använda de nya tilläggsmetoderna.

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-tilläggsmetod

Med den nya WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) tilläggsmetoden kan du enkelt införa ändringar i serialiseringskontrakten för godtyckliga IJsonTypeInfoResolver instanser.

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

Nya JsonContent.Create-överlagringar

Nu kan du skapa JsonContent instanser med trimsäkra eller källgenererade kontrakt. De nya metoderna är:

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

Låsa en JsonSerializerOptions-instans

Med följande nya metoder kan du styra när en JsonSerializerOptions instans är låst:

  • JsonSerializerOptions.MakeReadOnly()

    Den här överlagringen är utformad för att vara trimsäker och utlöser därför ett undantag i fall där alternativinstansen inte har konfigurerats med en lösning.

  • JsonSerializerOptions.MakeReadOnly(Boolean)

    Om du skickar true till den här överbelastningen fylls alternativinstansen i med standardreflexeraren om en saknas. Den här metoden är markerad RequiresUnreferenceCode/RequiresDynamicCode och är därför olämplig för interna AOT-program.

Med den nya IsReadOnly egenskapen kan du kontrollera om alternativinstansen är låst.

Tidsabstraktion

Den nya TimeProvider klassen och ITimer gränssnittet lägger till funktioner för tidsabstraktion , vilket gör att du kan simulera tid i testscenarier. Dessutom kan du använda tidsabstraktionen för att håna Task åtgärder som förlitar sig på tidsförlopp med hjälp av Task.Delay och Task.WaitAsync. Tidsabstraktionen stöder följande viktiga tidsåtgärder:

  • Hämta lokal tid och UTC-tid
  • Hämta en tidsstämpel för att mäta prestanda
  • Skapa en timer

Följande kodfragment visar några användningsexempel.

// 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-förbättringar

Om du vill aktivera skrivning av en strängliknande representation av din typ till ett målintervall implementerar du det nya IUtf8SpanFormattable gränssnittet på din typ. Det nya gränssnittet är nära relaterat till , men riktar sig till ISpanFormattableUTF8 och Span<byte> i stället för UTF16 och Span<char>.

IUtf8SpanFormattable har implementerats på alla primitiva typer (plus andra), med exakt samma delade logik oavsett om du riktar in dig på string, Span<char>eller Span<byte>. Den har fullt stöd för alla format (inklusive den nya binära B-specificeraren) och alla kulturer. Det innebär att du nu kan formatera direkt till UTF8 från Byte, Complex, Char, DateOnly, DateTime, DecimalDoubleDateTimeOffsetGuidHalf, , IPAddress, Int128NFloatInt16IPNetworkSByteInt64IntPtrSingleInt32Rune, TimeOnly, TimeSpan, UInt16, UInt32, , UInt64, UInt128, UIntPtroch .Version

Nya Utf8.TryWrite metoder ger en UTF8-baserad motsvarighet till de befintliga MemoryExtensions.TryWrite metoderna, som är UTF16-baserade. Du kan använda interpolerad strängsyntax för att formatera ett komplext uttryck direkt till ett intervall på UTF8 byte, till exempel:

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

Implementeringen identifierar IUtf8SpanFormattable formatvärdena och använder sina implementeringar för att skriva utF8-representationer direkt till målintervallet.

Implementeringen använder också den nya Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32) metoden, som tillsammans med dess Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32) motsvarighet stöder kodning och avkodning till ett målintervall. Om intervallet inte är tillräckligt långt för att hålla det resulterande tillståndet returnerar false metoderna i stället för att utlösa ett undantag.

Metoder för att arbeta med slumpmässighet

Typerna System.Random och System.Security.Cryptography.RandomNumberGenerator introducerar två nya metoder för att arbeta med slumpmässighet.

GetItems<T>()

Med de nya System.Random.GetItems metoderna och System.Security.Cryptography.RandomNumberGenerator.GetItems kan du slumpmässigt välja ett angivet antal objekt från en indatauppsättning. I följande exempel visas hur du använder System.Random.GetItems<T>() (på den instans som tillhandahålls av Random.Shared egenskapen) för att slumpmässigt infoga 31 objekt i en matris. Det här exemplet kan användas i ett spel av "Simon" där spelarna måste komma ihåg en sekvens med färgade knappar.

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

Med de nya Random.Shuffle metoderna och RandomNumberGenerator.Shuffle<T>(Span<T>) kan du randomisera ordningen på ett spann. Dessa metoder är användbara för att minska träningsfördomar i maskininlärning (så det första är inte alltid träning, och det sista alltid testa).

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

Prestandafokuserade typer

.NET 8 introducerar flera nya typer som syftar till att förbättra appprestanda.

  • Det nya System.Collections.Frozen namnområdet innehåller samlingstyperna FrozenDictionary<TKey,TValue> och FrozenSet<T>. Dessa typer tillåter inte några ändringar av nycklar och värden när en samling har skapats. Det kravet möjliggör snabbare läsåtgärder (till exempel TryGetValue()). Dessa typer är särskilt användbara för samlingar som fylls i vid första användningen och sedan sparas under en långlivad tjänst, till exempel:

    private static readonly FrozenDictionary<string, bool> s_configurationData =
        LoadConfigurationData().ToFrozenDictionary(optimizeForReads: true);
    
    // ...
    if (s_configurationData.TryGetValue(key, out bool setting) && setting)
    {
        Process();
    }
    
  • Metoder som MemoryExtensions.IndexOfAny söker efter den första förekomsten av ett värde i den skickade samlingen. Den nya System.Buffers.SearchValues<T> typen är utformad för att skickas till sådana metoder. På motsvarande sätt lägger .NET 8 till nya överlagringar av metoder som MemoryExtensions.IndexOfAny att acceptera en instans av den nya typen. När du skapar en instans av SearchValues<T>härleds alla data som behövs för att optimera efterföljande sökningar vid den tidpunkten, vilket innebär att arbetet utförs i förväg.

  • Den nya System.Text.CompositeFormat typen är användbar för att optimera formatsträngar som inte är kända vid kompileringstiden (till exempel om formatsträngen läses in från en resursfil). Lite extra tid spenderas i förväg för att utföra arbete som att parsa strängen, men det sparar arbetet från att utföras vid varje användning.

    private static readonly CompositeFormat s_rangeMessage =
        CompositeFormat.Parse(LoadRangeMessageResource());
    
    // ...
    static string GetMessage(int min, int max) =>
        string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
    
  • Nya System.IO.Hashing.XxHash3 typer och System.IO.Hashing.XxHash128 ger implementeringar av de snabba XXH3- och XXH128-hashalgoritmerna.

System.Numerics och System.Runtime.Intrinsics

Det här avsnittet beskriver förbättringar av namnrymderna System.Numerics och System.Runtime.Intrinsics .

  • Vector256<T>, Matrix3x2och Matrix4x4 har förbättrat maskinvaruaccelerationen på .NET 8. Till exempel Vector256<T> omimplementerades till att internt vara 2x Vector128<T> åtgärder, där det är möjligt. Detta möjliggör partiell acceleration av vissa funktioner när Vector128.IsHardwareAccelerated == true men Vector256.IsHardwareAccelerated == false, till exempel på Arm64.
  • Maskinvaruinbyggda objekt kommenteras nu med attributet ConstExpected . Detta säkerställer att användarna är medvetna om när den underliggande maskinvaran förväntar sig en konstant och därför när ett icke-konstant värde oväntat kan skada prestandan.
  • API:et Lerp(TSelf, TSelf, TSelf)Lerp har lagts till IFloatingPointIeee754<TSelf> i och därför till float (Single), double (Double) och Half. Med det här API:et kan en linjär interpolering mellan två värden utföras effektivt och korrekt.

Vector512 och AVX-512

.NET Core 3.0 utökat SIMD-stöd för att inkludera plattformsspecifika maskinvaru-API:er för x86/x64. .NET 5 har lagt till stöd för Arm64 och .NET 7 och lagt till den plattformsoberoende maskinvaran. .NET 8 utökar SIMD-stödet genom att introducera Vector512<T> och stödja instruktioner för Intel Advanced Vector Extensions 512 (AVX-512).

Mer specifikt innehåller .NET 8 stöd för följande viktiga funktioner i AVX-512:

  • 512-bitars vektoråtgärder
  • Ytterligare 16 SIMD-register
  • Ytterligare instruktioner för 128-bitars, 256- och 512-bitarsvektorer

Om du har maskinvara som stöder funktionerna rapporterar truenu Vector512.IsHardwareAccelerated .

.NET 8 lägger också till flera plattformsspecifika klasser under System.Runtime.Intrinsics.X86 namnområdet:

Dessa klasser följer samma allmänna form som andra instruktionsuppsättningsarkitekturer (ISA) eftersom de exponerar en IsSupported egenskap och en kapslad Avx512F.X64 klass för instruktioner som endast är tillgängliga för 64-bitarsprocesser. Dessutom har varje klass en kapslad Avx512F.VL klass som exponerar tilläggen Avx512VL (vektorlängd) för motsvarande instruktionsuppsättning.

Även om du inte uttryckligen använder Vector512-specifika eller Avx512F-specifika instruktioner i koden, kommer du förmodligen fortfarande att dra nytta av det nya AVX-512-stödet. JIT kan dra nytta av ytterligare register och instruktioner implicit när du använder Vector128<T> eller Vector256<T>. Basklassbiblioteket använder den här maskinvaran internt i de flesta åtgärder som exponeras av Span<T> och ReadOnlySpan<T> i många av de matematiska API:er som exponeras för primitiva typer.

Datavalidering

Namnområdet System.ComponentModel.DataAnnotations innehåller nya dataverifieringsattribut som är avsedda för valideringsscenarier i molnbaserade tjänster. Medan de befintliga DataAnnotations validerarna är inriktade på typisk validering av UI-datainmatning, till exempel fält i ett formulär, är de nya attributen utformade för att verifiera data som inte är användarinmatningsdata, till exempel konfigurationsalternativ. Utöver de nya attributen har nya egenskaper lagts till i typerna RangeAttribute och RequiredAttribute .

Nytt API beskrivning
RangeAttribute.MinimumIsExclusive
RangeAttribute.MaximumIsExclusive
Anger om gränser ingår i det tillåtna intervallet.
System.ComponentModel.DataAnnotations.LengthAttribute Anger både nedre och övre gränser för strängar eller samlingar. Till exempel [Length(10, 20)] kräver minst 10 element och högst 20 element i en samling.
System.ComponentModel.DataAnnotations.Base64StringAttribute Verifierar att en sträng är en giltig Base64-representation.
System.ComponentModel.DataAnnotations.AllowedValuesAttribute
System.ComponentModel.DataAnnotations.DeniedValuesAttribute
Ange tillåtna listor respektive neka listor. Exempel: [AllowedValues("apple", "banana", "mango")]

Mått

Med nya API:er kan du koppla nyckel/värde-partaggar till Meter och Instrument objekt när du skapar dem. Aggregeringar av publicerade måttmätningar kan använda taggarna för att särskilja de aggregerade värdena.

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

Bland de nya API:erna finns:

Kryptografi

.NET 8 lägger till stöd för SHA-3-hash-primitiverna. (SHA-3 stöds för närvarande av Linux med OpenSSL 1.1.1 eller senare och Windows 11 Build 25324 eller senare.) API:er där SHA-2 är tillgängligt erbjuder nu en SHA-3-komplimang. Detta inkluderar SHA3_256, SHA3_384, och SHA3_512 för hashing, HMACSHA3_256, HMACSHA3_384och HMACSHA3_512 för HMAC; HashAlgorithmName.SHA3_256, HashAlgorithmName.SHA3_384, och HashAlgorithmName.SHA3_512 för hashing där algoritmen kan konfigureras, och RSAEncryptionPadding.OaepSHA3_256, RSAEncryptionPadding.OaepSHA3_384och RSAEncryptionPadding.OaepSHA3_512 för RSA OAEP-kryptering.

I följande exempel visas hur du använder API:erna, inklusive SHA3_256.IsSupported egenskapen för att avgöra om plattformen stöder SHA-3.

// 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-stöd syftar för närvarande till att stödja kryptografiska primitiver. Konstruktioner och protokoll på högre nivå förväntas inte ha fullt stöd för SHA-3 från början. Dessa protokoll omfattar X.509-certifikat, SignedXmloch COSE.

Nätverk

Stöd för HTTPS-proxy

Hittills har proxytyperna som HttpClient har stöd för alla tillåtit en "man-in-the-middle" att se vilken plats klienten ansluter till, även för HTTPS-URI:er. HttpClient stöder nu HTTPS-proxy, vilket skapar en krypterad kanal mellan klienten och proxyn så att alla begäranden kan hanteras med fullständig sekretess.

Om du vill aktivera HTTPS-proxy anger du all_proxy miljövariabeln eller använder WebProxy klassen för att styra proxyn programmatiskt.

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

Du kan också använda WebProxy klassen för att styra proxyn programmatiskt.

Stream-baserade ZipFile-metoder

.NET 8 innehåller nya överlagringar som ZipFile.CreateFromDirectory gör att du kan samla in alla filer som ingår i en katalog och zippa dem och sedan lagra den resulterande zip-filen i den angivna strömmen. På samma sätt kan du med nya ZipFile.ExtractToDirectory överlagringar tillhandahålla en ström som innehåller en zippad fil och extrahera innehållet i filsystemet. Det här är de nya överlagringarna:

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

Dessa nya API:er kan vara användbara när diskutrymmet är begränsat, eftersom de undviker att behöva använda disken som ett mellanliggande steg.

Tilläggsbibliotek

Det här avsnittet innehåller följande underavsnitt:

Nyckelade DI-tjänster

Nyckelbaserade beroendeinmatningstjänster (DI) är ett sätt att registrera och hämta DI-tjänster med hjälp av nycklar. Med hjälp av nycklar kan du omfångsbegränsa hur du registrerar och använder tjänster. Det här är några av de nya API:erna:

I följande exempel visas hur du använder nyckelade DI-tjänster.

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

Mer information finns i dotnet/runtime#64427.

Värdbaserade livscykeltjänster

Värdbaserade tjänster har nu fler alternativ för körning under programmets livscykel. IHostedService tillhandahålls StartAsync och StopAsync, och tillhandahåller nu IHostedLifecycleService följande ytterligare metoder:

Dessa metoder körs före respektive efter de befintliga punkterna.

I följande exempel visas hur du använder de nya API:erna.

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

Mer information finns i dotnet/runtime#86511.

Alternativvalidering

Källgenerator

För att minska startkostnaderna och förbättra verifieringsfunktionsuppsättningen har vi introducerat en källkodsgenerator som implementerar valideringslogik. Följande kod visar exempelmodeller och validatorklasser.

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

Om din app använder beroendeinmatning kan du mata in valideringen enligt följande exempelkod.

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 introducerar typen ValidateOptionsResultBuilder för att underlätta skapandet av ett ValidateOptionsResult objekt. Det är viktigt att den här byggaren tillåter ackumulering av flera fel. Tidigare var det svårt att skapa det ValidateOptionsResult objekt som krävs för att implementera IValidateOptions<TOptions>.Validate(String, TOptions) och resulterade ibland i skiktade valideringsfel. Om det fanns flera fel stoppades valideringsprocessen ofta vid det första felet.

Följande kodfragment visar ett exempel på användning av 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-konstruktorer

LoggerMessageAttribute erbjuder nu ytterligare överlagringar av konstruktorn. Tidigare var du tvungen att välja antingen den parameterlösa konstruktorn eller konstruktorn som krävde alla parametrar (händelse-ID, loggnivå och meddelande). De nya överlagringarna ger större flexibilitet när det gäller att ange nödvändiga parametrar med reducerad kod. Om du inte anger något händelse-ID genererar systemet ett automatiskt.

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

Tilläggsmått

IMeterFactory-gränssnitt

Du kan registrera det nya IMeterFactory gränssnittet i DI-containrar (dependency injection) och använda det för att skapa Meter objekt på ett isolerat sätt.

IMeterFactory Registrera till DI-containern med standardimplementeringen av mätarfabriken:

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

Konsumenterna kan sedan hämta mätarfabriken och använda den för att skapa ett nytt Meter objekt.

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

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

Meter meter = meterFactory.Create(options);

MetricCollector<T-klass>

Med den nya MetricCollector<T> klassen kan du registrera måttmätningar tillsammans med tidsstämplar. Dessutom erbjuder klassen flexibiliteten att använda en valfri tidsleverantör för korrekt tidsstämpelgenerering.

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

Det uppdaterade NuGet-paketet System.Numerics.Tensors innehåller API:er i det nya TensorPrimitives namnområdet som lägger till stöd för tensor-åtgärder. Tensor-primitiverna optimerar dataintensiva arbetsbelastningar som AI och maskininlärning.

AI-arbetsbelastningar som semantisk sökning och hämtningsförhöjd generering (RAG) utökar funktionerna för naturligt språk i stora språkmodeller som ChatGPT genom att utöka frågor med relevanta data. För de här arbetsbelastningarna är åtgärder på vektorer , till exempel cosinélikhet för att hitta de mest relevanta data som ska besvara en fråga, avgörande. Paketet System.Numerics.Tensors.TensorPrimitives innehåller API:er för vektoråtgärder, vilket innebär att du inte behöver använda ett externt beroende eller skriva en egen implementering.

Det här paketet ersätter Paketet System.Numerics.Tensors.

Mer information finns i blogginlägget Om att tillkännage .NET 8 RC 2.

Se även