Dela via


Vad är nytt i .NET 8 runtime-miljön

I den här artikeln beskrivs nya funktioner i .NET runtime i .NET 8.

Prestandaförbättringar

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

  • 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 operanders tupplar är placerade 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 struktur-optimering

.NET 8 innehåller ett nytt optimeringspass för fysisk optimering av kodgenerering som generaliserar JIT-kompilatorns möjlighet att främja struktvariabler. 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 RefreshMemoryLimit()-API:et 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 RefreshMemoryLimit-anropet 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 ett InvalidOperationException-fel om den hårda gränsen är ogiltig, till exempel om procentandelar för heapens hårda gräns är negativa eller 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 antyder av förändringar i containerminnesgränsen, är lägre än vad som redan har allokerats.

Globalisering för mobilappar

Mobilappar på iOS, tvOS och MacCatalyst kan välja ett nytt hybrid- globaliseringslä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 mobila.

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 egenskapen HybridGlobalization 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-interoperabilitet

.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 LibraryImportAttributeoch du kan använda typer med GeneratedComInterfaceAttribute som parametrar och returtyper 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 attributet GeneratedComClassAttribute så att du kan skicka typer som implementerar gränssnitt med attributet GeneratedComInterfaceAttribute 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 attributet GeneratedComInterfaceAttribute stöder alla samma typer som LibraryImportAttribute, och LibraryImportAttribute stöder nu GeneratedComInterface-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 exponeras för ohanterad kod, kan du använda alternativen i egenskapen Options 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öd för 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 ComImportAttributeerbjuder 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 GeneratedComInterfaceAttributeerbjuder en glödlampa ett alternativ för att lägga till attributet GeneratedComClassAttribute till typen. När dina typer har konverterats kan du flytta dina DllImport metoder för att använda LibraryImportAttribute.

Begränsningar

COM-källkodsgeneratorn stöder inte lägenhetsanslutning. Den använder nyckelordet new för att aktivera en COM CoClass, samt de följande API:erna:

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ällgeneratorn söker igenom Configure(TOptions), Bindoch Get-anrop för att hämta typinformation. 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-kompilerade webbappar och när PublishTrimmed är inställt på true (.NET 8+ appar). För andra projekttyper är källgeneratorn inaktiverad som standard, men du kan välja genom att ange egenskapen EnableConfigurationBindingGenerator till true i projektfilen:

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

Följande kod visar ett exempel på anrop av bindern.

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

de core .NET-biblioteken

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

Reflektion

Funktionspekare introducerades i .NET 5 lades dock inte motsvarande stöd för reflektion till vid den tidpunkten. När du använder typeof eller reflektion på en funktionspekare, till exempel typeof(delegate*<void>()) eller FieldInfo.FieldType, 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.

Notis

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

Den nya funktionen är för närvarande implementerad endast i CoreCLR-runtime och MetadataLoadContext.

Nya API:er har lagts till i System.Type, till exempel 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

Serialisering

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 JSON-egenskaper som inte finns i POCO-.

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ärdena. byte-värden serialiseras till Base64-strängar och andra typer till JSON-arrayer.

    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 source generator som syftar till att göra Native AOT-upplevelsen jämförbar med den reflektionsbaserade serialiseraren. Till exempel:

  • Källgeneratorn stöder nu serialiseringstyper med egenskaper för required och init. 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 kapsling av JsonSerializerContext-deklarationer inom godtyckliga typer.

  • Stöd för kompilatorgenererade eller obeskringliga typer i svagt typerade 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 annotera 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 egenskapen JsonConverter.Type kan du slå upp 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 egenskap för TypeInfoResolverChain som kompletterar den befintliga egenskapen TypeInfoResolver. 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. Du kan också använda TypeInfoResolverChain för att granska kedjan eller ta bort komponenter från den. Mer information finns i Kombinera källgeneratorer.

Dessutom är JsonSerializerOptions.AddContext<TContext>() nu föråldrad. Det har ersatts av egenskaperna TypeInfoResolver och TypeInfoResolverChain. För mer information, se 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 en set-accessor).

För att aktivera detta stöd globalt ställer du in det nya alternativet, PreferredObjectCreationHandling, till JsonObjectCreationHandling.Populate. Om kompatibilitet är ett problem kan du också aktivera funktionen mer detaljerat genom att placera attributet [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] på specifika typer vars egenskaper ska fyllas i eller på enskilda egenskaper.

Tänk till exempel på följande kod som deserialiserar till en typ av CustomerInfo 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"
    };
}

Innan .NET 8 ignorerades indatavärdena och egenskaperna Names 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 fylla i deserialiseringsbeteende finns i Fylla i initierade egenskaper.

Inaktivera reflektionsbaserad standard

Nu kan du inaktivera att använda 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 anger du egenskapen JsonSerializerIsReflectionEnabledByDefault 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 lita på att egenskapen konfigurerar 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 inkludera icke-offentliga medlemmar i serialiseringskontraktet för en viss typ med hjälp av JsonIncludeAttribute- och JsonConstructorAttribute-attributmarkeringar.

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

För mer information, se Använd oföränderliga typer och icke-offentliga medlemmar och accessorer.

API:er för strömmande deserialisering

.NET 8 innehåller nya metoder för IAsyncEnumerable<T> strömmande deserialiseringstillä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-metodö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 denna överbelastning, fylls instansen av alternativ med standardreflektionsupplösaren 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 egenskapen IsReadOnly kan du kontrollera om alternativinstansen är låst.

Tidsabstraktion

Det nya TimeProvider-klass- och ITimer-gränssnittet lägger till tidsabstraktion funktioner, 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 för din typ. Det här nya gränssnittet är nära relaterat till ISpanFormattable, men riktar sig till UTF8 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ärspecificeraren "B") och alla kulturer. Det innebär att du nu kan formatera direkt till UTF8 från 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, 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 på formatvärdena och använder sina implementeringar för att skriva utF8-representationer direkt till målintervallet.

Implementeringen använder också den nya metoden Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32), som tillsammans med sin 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 metoderna false 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 metoderna System.Random.GetItems 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 egenskapen Random.Shared) 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 ...

Blanda<T>()

Med de nya metoderna Random.Shuffle och RandomNumberGenerator.Shuffle<T>(Span<T>) kan du randomisera intervallets ordning. Dessa metoder är användbara för att minska träningsbias i maskininlärning, så att det första inte alltid är träning och det sista alltid test.

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();
    
    // ...
    if (s_configurationData.TryGetValue(key, out bool setting) && setting)
    {
        Process();
    }
    
  • Metoder som MemoryExtensions.IndexOfAny letar efter den första förekomsten av vilket värde som helst i den angivna samlingen. Den nya typen System.Buffers.SearchValues<T> ä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 som accepterar 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 analysera strängen, men det förhindrar att arbetet måste utföras vid varje användningstillfälle.

    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- och System.IO.Hashing.XxHash128-typer ger implementeringar av de snabba XXH3- och XXH128-hashalgoritmerna.

System.Numerics och System.Runtime.Intrinsics

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

  • Vector256<T>, Matrix3x2och Matrix4x4 har förbättrat maskinvaruaccelerationen på .NET 8. Till exempel omimplementerades Vector256<T> internt till 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.
  • Lerp(TSelf, TSelf, TSelf) Lerp-API:et har lagts till i IFloatingPointIeee754<TSelf> 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 förbättrar stöd för SIMD genom att införa Vector512<T> och stöd för 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 Vector512.IsHardwareAccelerated nu true.

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

Dessa klasser följer samma allmänna form som andra arkitekturer för instruktionsuppsättningar (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 tillgängliggör 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 dessa maskinvarufunktioner internt i de flesta operationer som exponeras av Span<T> och ReadOnlySpan<T> och i många av de matematik-API:er som exponeras för de primitiva typerna.

Datavalidering

Namnområdet System.ComponentModel.DataAnnotations innehåller nya dataverifieringsattribut som är avsedda för valideringsscenarier i molnbaserade tjänster. Medan de befintliga DataAnnotations validatorerna är inriktade på typisk validering av användargränssnittets 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 RangeAttribute typ.

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 kräver [Length(10, 20)] 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 blockera listor. Till exempel [AllowedValues("apple", "banana", "mango")].

Mätvärden

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 omfattar SHA3_256, SHA3_384och SHA3_512 för hashning. HMACSHA3_256, HMACSHA3_384och HMACSHA3_512 för HMAC; HashAlgorithmName.SHA3_256, HashAlgorithmName.SHA3_384och HashAlgorithmName.SHA3_512 för hashning 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 egenskapen SHA3_256.IsSupported 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ätverkande

Stöd för HTTPS-proxy

Hittills har proxytyperna som HttpClient stöd för alla tillåtit en "man-in-the-middle" för att se vilken plats klienten ansluter till, även för HTTPS-URI:er. HttpClient stöder nu HTTPS-proxy, som 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 miljövariabeln all_proxy eller använder klassen WebProxy 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 klassen WebProxy för att styra proxyn programmatiskt.

Stream-baserade ZipFile-metoder

.NET 8 innehåller nya överlagringar av ZipFile.CreateFromDirectory som 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 de nya ZipFile.ExtractToDirectory-överbelastningarna tillhandahålla en ström som innehåller en zippad fil och extrahera dess innehåll till filsystemet. Det här är de nya överbelastningarna:

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.

Hostade livscykeltjänster

Värdbaserade tjänster har nu fler alternativ för exekvering under applikationens livscykel. IHostedService tillhandahöll StartAsync och StopAsync, och IHostedLifecycleService tillhandahåller nu 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 ValidateOptionsResultBuilder typ 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 konstruktoröverlagringar. 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 (beroendeinmatning) och använda det för att skapa Meter objekt på ett isolerat sätt.

Registrera IMeterFactory i DI-containern med den standardmässiga fabriksmetoden för mätare:

// '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 klassen MetricCollector<T> 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 System.Numerics.Tensors NuGet-paketet innehåller API:er av den nya System.Numerics.Tensors.TensorPrimitives typen 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, till exempel ChatGPT, genom att utöka frågor med relevanta data. För dessa arbetsbelastningar är åtgärder på vektorer – som cosinuslikhet för att hitta den mest relevanta datan för att besvara en specifik fråga – avgörande. Den TensorPrimitives typen innehåller API:er för vektoråtgärder.

Mer information finns i blogginlägget Announcing .NET 8 RC 2.

Internt AOT-stöd

Alternativet att publicera som Native AOT- introducerades först i .NET 7. När du publicerar en app med intern AOT skapas en helt fristående version av din app som inte behöver någon körning – allt ingår i en enda fil. .NET 8 ger följande förbättringar för Native AOT-publicering:

  • Lägger till stöd för x64- och Arm64-arkitekturerna på macOS-.

  • Minskar storleken på inbyggda AOT-appar på Linux med upp till 50%. I följande tabell visas storleken på en "Hello World"-app som distribuerats med Native AOT och som innehåller hela .NET-runtime på .NET 7 jämfört med .NET 8.

    Operativsystem .NET 7 .NET 8
    Linux x64 (med -p:StripSymbols=true) 3,76 MB 1,84 MB
    Windows x64 2,85 MB 1,77 MB
  • Gör att du kan ange en optimeringsinställning: storlek eller hastighet. Som standard väljer kompilatorn att generera snabb kod samtidigt som den är medveten om programmets storlek. Du kan dock använda egenskapen <OptimizationPreference> MSBuild för att optimera specifikt för den ena eller den andra. Mer information finns i Optimera AOT-distributioner.

Rikta in dig på iOS-liknande plattformar med inbyggd AOT

.NET 8 startar arbetet med att aktivera internt AOT-stöd för iOS-liknande plattformar. Nu kan du skapa och köra .NET iOS- och .NET MAUI-program med intern AOT på följande plattformar:

  • ios
  • iossimulator
  • maccatalyst
  • tvos
  • tvossimulator

Preliminära tester visar att appstorleken på disken minskar med cirka 35% för .NET iOS-appar som använder intern AOT i stället för Mono. Appstorleken på disken för .NET MAUI iOS-appar minskar med upp till 50%. Dessutom är starttiden också snabbare. .NET iOS-appar har cirka 28% snabbare starttid, medan .NET MAUI iOS-appar har cirka 50% bättre startprestanda jämfört med Mono. .NET 8-stödet är experimentellt och bara det första steget för funktionen som helhet. Mer information finns i .NET 8 Prestandaförbättringar i .NET MAUI-blogginlägget.

Inbyggt AOT-stöd är tillgängligt som en opt-in-funktion som är avsedd för appdistribution. Mono är fortfarande standardkörningen för apputveckling och distribution. Om du vill skapa och köra ett .NET MAUI-program med intern AOT på en iOS-enhet använder du dotnet workload install maui för att installera .NET MAUI-arbetsbelastningen och dotnet new maui -n HelloMaui för att skapa appen. Ange sedan egenskapen MSBuild PublishAot till true i projektfilen.

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

När du ställer in den nödvändiga egenskapen och kör dotnet publish enligt följande exempel, kommer appen att distribueras med hjälp av Native AOT.

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

Begränsningar

Alla iOS-funktioner är inte kompatibla med intern AOT. På samma sätt är inte alla bibliotek som ofta används i iOS kompatibla med NativeAOT. Och förutom de befintliga begränsningarna för Native AOT-distributionvisar följande lista några av de andra begränsningarna när du riktar in dig på iOS-liknande plattformar.

  • Användning av intern AOT aktiveras endast under appdistributionen (dotnet publish).
  • Felsökning av hanterad kod stöds endast med Mono.
  • Kompatibiliteten med .NET MAUI-ramverket är begränsad.

AOT-kompilering för Android-appar

För att minska appstorleken använder .NET- och .NET MAUI-appar för Android profilerat AOT-kompileringsläge när de byggs i Release-läge. Profilerad AOT-kompilering påverkar färre metoder än vanlig AOT-kompilering. .NET 8 introducerar egenskapen <AndroidStripILAfterAOT> som gör att du kan välja ytterligare AOT-kompilering för Android-appar för att minska appstorleken ännu mer.

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

Som standard åsidosätter inställningen AndroidStripILAfterAOT till true standardinställningen AndroidEnableProfiledAot, vilket gör att (nästan) alla metoder som AOT-kompilerats kan trimmas. Du kan också använda profilerad AOT och IL-borttagning tillsammans genom att uttryckligen ställa in båda egenskaperna till true:

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

Korsbyggda Windows-appar

När du skapar appar som riktar in sig på Windows på icke-Windows-plattformar uppdateras den resulterande körbara filen nu med alla angivna Win32-resurser, till exempel programikon, manifest, versionsinformation.

Tidigare var program tvungna att byggas på Windows för att ha sådana resurser. Att åtgärda den här klyftan i stöd för korsbyggande har varit en populär begäran, eftersom det var en betydande smärtpunkt som påverkade både infrastrukturkomplexitet och resursanvändning.

Se även