Novità del runtime di .NET 8
Questo articolo descrive le nuove funzionalità del runtime .NET per .NET 8.
Miglioramenti delle prestazioni
.NET 8 include miglioramenti alla generazione di codice e alla compilazione Just-in-Time (JIT):
- Miglioramenti delle prestazioni di Arm64
- Miglioramenti di SIMD
- Supporto per le estensioni AVX-512 ISA (vedere Vector512 e AVX-512)
- Miglioramenti nativi del cloud
- Miglioramenti della velocità effettiva JIT
- Cicli e ottimizzazioni generali
- Accesso ottimizzato per i campi contrassegnati con ThreadStaticAttribute
- Allocazione di registri consecutivi. Arm64 include due istruzioni per la ricerca di vettori di tabella, che richiedono che tutte le entità nei rispettivi operandi tupla siano presenti in registri consecutivi.
- JIT/NativeAOT può ora srotolare e vettorizzare automaticamente alcune operazioni di memoria con SIMD, come confronto, copia e azzeramento, se riesce a determinarne le dimensioni in fase di compilazione.
Inoltre, l'ottimizzazione PGO (Dynamic Profile Guided Optimization) è stata migliorata ed è ora abilitata per impostazione predefinita. Non è più necessario usare un'opzione di configurazione di runtime per abilitarla. Il PGO dinamico lavora in stretta collaborazione con la compilazione a livelli per ottimizzare ulteriormente il codice, basandosi su ulteriore strumentazione inserita durante il livello 0.
In media, il PGO dinamico aumenta le prestazioni del 15%%. In una suite di benchmark di circa 4600 test, 23% hanno visto miglioramenti delle prestazioni di 20% o più.
Promozione della struct Codegen
.NET 8 include un nuovo passaggio di ottimizzazione della promozione fisica per la generazione del codice, che generalizza la capacità di JIT di promuovere le variabili di struct. Questa ottimizzazione (chiamata anche sostituzione scalare delle aggregazioni) sostituisce i campi delle variabili di struct con variabili primitive che il JIT è quindi in grado di comprendere e ottimizzare in modo più preciso.
Jit supporta già questa ottimizzazione, ma con diverse limitazioni di grandi dimensioni, tra cui:
- È stato supportato solo per gli struct con quattro o meno campi.
- È stato supportato solo se ogni campo era un tipo primitivo o una semplice struct che avvolge un tipo primitivo.
La promozione fisica elimina queste limitazioni, risolvendo una serie di problemi JIT di lunga durata.
Raccolta dei rifiuti
.NET 8 aggiunge una funzionalità per regolare il limite di memoria in tempo reale. Ciò è utile negli scenari di servizio cloud, in cui la domanda arriva e va. Per essere convenienti, i servizi devono aumentare e ridurre il consumo delle risorse man mano che la domanda varia. Quando un servizio rileva una riduzione della domanda, può ridurre il consumo di risorse riducendone il limite di memoria. In precedenza, ciò avrebbe avuto esito negativo perché il Garbage Collector (GC) non era a conoscenza della modifica e potrebbe allocare più memoria del nuovo limite. Con questa modifica, è possibile chiamare l'API RefreshMemoryLimit() per aggiornare GC con il nuovo limite di memoria.
Esistono alcune limitazioni da tenere presenti:
- Nelle piattaforme a 32 bit (ad esempio, Windows x86 e Linux ARM), .NET non è in grado di stabilire un nuovo limite rigido dell'heap se non ne esiste già uno.
- L'API potrebbe restituire un codice di stato diverso da zero che indica che l'aggiornamento non è riuscito. Questo può accadere se la riduzione è troppo aggressiva e non lascia spazio per la manovra del GC. In questo caso, provare a chiamare
GC.Collect(2, GCCollectionMode.Aggressive)
per ridurre l'utilizzo corrente della memoria e quindi riprovare. - Se si aumenta il limite di memoria oltre le dimensioni che il GC ritiene che il processo possa gestire durante l'avvio, la chiamata
RefreshMemoryLimit
avrà esito positivo, ma non sarà in grado di usare più memoria rispetto a quella che percepisce come limite.
Il frammento di codice seguente illustra come chiamare l'API.
GC.RefreshMemoryLimit();
È anche possibile aggiornare alcune delle impostazioni di configurazione GC correlate al limite di memoria. Il frammento di codice seguente imposta il limite rigido dell'heap su 100 mebibyte (MiB):
AppContext.SetData("GCHeapHardLimit", (ulong)100 * 1_024 * 1_024);
GC.RefreshMemoryLimit();
L'API può generare un InvalidOperationException se il limite massimo non è valido, ad esempio nel caso di percentuali di limite massimo negative della memoria heap e se il limite massimo è troppo basso. Ciò può verificarsi se il limite rigido dell'heap impostato dall'aggiornamento, a causa delle nuove impostazioni AppData o implicite dalle modifiche al limite di memoria del contenitore, è inferiore a quello già eseguito.
Globalizzazione per le app per dispositivi mobili
Le app su iOS, tvOS e MacCatalyst possono optare per una nuova modalità di globalizzazione ibrida che utilizza un bundle ICU più leggero. In modalità ibrida, i dati di globalizzazione vengono parzialmente estratti dal bundle di ICU e parzialmente dalle chiamate alle API native. La modalità ibrida serve tutte le località supportate dai dispositivi mobili.
La modalità ibrida è più adatta per le app che non possono funzionare in modalità di globalizzazione invariante e che utilizzano culture escluse nei dati ICU su dispositivi mobili. È anche possibile usarlo quando si vuole caricare un file di dati di ICU più piccolo. Il file di icudt_hybrid.dat è 34,5 % più piccolo del file di dati ICU predefinito icudt.dat.
Per usare la modalità di globalizzazione ibrida, impostare la proprietà HybridGlobalization
MSBuild su true:
<PropertyGroup>
<HybridGlobalization>true</HybridGlobalization>
</PropertyGroup>
Esistono alcune limitazioni da tenere presenti:
- A causa delle limitazioni dell'API nativa, non tutte le API di globalizzazione sono supportate in modalità ibrida.
- Alcune api supportate hanno un comportamento diverso.
Per verificare se l'applicazione è interessata, consultare Differenze comportamentali.
Interoperabilità COM generata dal codice sorgente
.NET 8 include un nuovo generatore di origine che supporta l'interoperabilità con le interfacce COM. È possibile utilizzare il GeneratedComInterfaceAttribute per contrassegnare un'interfaccia come interfaccia COM per il generatore di origine. Il generatore di origine genererà quindi il codice per abilitare la chiamata dal codice C# al codice non gestito. Genera anche il codice per abilitare la chiamata da codice non gestito in C#. Questo generatore di origine si integra con LibraryImportAttributeed è possibile usare i tipi con l'GeneratedComInterfaceAttribute come parametri e i tipi restituiti nei metodi con attributi LibraryImport
.
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
[GeneratedComInterface]
[Guid("5401c312-ab23-4dd3-aa40-3cb4b3a4683e")]
partial interface IComInterface
{
void DoWork();
}
internal partial class MyNativeLib
{
[LibraryImport(nameof(MyNativeLib))]
public static partial void GetComInterface(out IComInterface comInterface);
}
Il generatore di origine supporta anche il nuovo attributo GeneratedComClassAttribute per consentire di passare tipi che implementano interfacce con l'attributo GeneratedComInterfaceAttribute al codice non gestito. Il generatore di origine genererà il codice necessario per esporre un oggetto COM che implementa le interfacce e inoltra le chiamate all'implementazione gestita.
I metodi sulle interfacce con l'attributo GeneratedComInterfaceAttribute supportano tutti gli stessi tipi di LibraryImportAttribute
e LibraryImportAttribute
ora supporta i tipi con attributi GeneratedComInterface
e GeneratedComClass
.
Se il codice C# usa solo un'interfaccia con attributi GeneratedComInterface
per eseguire il wrapping di un oggetto COM da codice non gestito o eseguire il wrapping di un oggetto gestito da C# per esporre al codice non gestito, è possibile usare le opzioni nella proprietà Options per personalizzare il codice che verrà generato. Queste opzioni indicano che non è necessario scrivere marshaller per scenari che non verranno usati.
Il generatore di origine usa il nuovo tipo di StrategyBasedComWrappers per creare e gestire i wrapper di oggetti COM e i wrapper di oggetti gestiti. Questo nuovo tipo gestisce l'esperienza utente .NET prevista per l'interoperabilità COM, fornendo al tempo stesso punti di personalizzazione per gli utenti avanzati. Se l'applicazione ha un proprio meccanismo per la definizione dei tipi da COM o se è necessario supportare scenari non supportati da COM generati dall'origine, è consigliabile usare il nuovo tipo di StrategyBasedComWrappers per aggiungere le funzionalità mancanti per lo scenario e ottenere la stessa esperienza utente .NET per i tipi COM.
Se si usa Visual Studio, i nuovi analizzatori e correzioni del codice semplificano la conversione del codice di interoperabilità COM esistente per l'uso dell'interoperabilità generata dall'origine. Accanto a ogni interfaccia con il ComImportAttribute, una lampadina propone la possibilità di convertire in interop generato dalla sorgente. La correzione modifica l'interfaccia in modo da usare l'attributo GeneratedComInterfaceAttribute. Accanto a ogni classe che implementa un'interfaccia con GeneratedComInterfaceAttribute
, una lampadina offre un'opzione per aggiungere l'attributo GeneratedComClassAttribute al tipo. Dopo aver convertito i tipi, è possibile trasferire i metodi di DllImport
per utilizzare LibraryImportAttribute
.
Limitazioni
Il generatore di origine COM non supporta l'affinità di appartamento, utilizzando la parola chiave new
per attivare una CoClass COM e le seguenti API:
- interfacce basate su IDispatch.
- Interfacce basate su IInspectable.
- Proprietà ed eventi COM.
Generatore di origine dell'associazione di configurazione
.NET 8 introduce un generatore di origine per fornire configurazione AOT e trim-friendly in ASP.NET Core. Il generatore è un'alternativa all'implementazione preesistente basata su riflessione.
Il generatore di origine esegue il sondaggio per le chiamate Configure(TOptions), Binde Get per recuperare informazioni sul tipo. Quando il generatore è abilitato in un progetto, il compilatore sceglie in modo implicito i metodi generati rispetto alle implementazioni di framework preesistenti basate sulla riflessione.
Non sono necessarie modifiche al codice sorgente per usare il generatore. È abilitato per impostazione predefinita nelle app Web compilate da AOT e quando PublishTrimmed
è impostato su true
(app.NET 8+). Per altri tipi di progetto, il generatore di origine è disattivato per impostazione predefinita, ma è possibile acconsentire esplicitamente impostando la proprietà EnableConfigurationBindingGenerator
su true
nel file di progetto:
<PropertyGroup>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
Il codice seguente mostra un esempio di invocazione del binder.
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; }
}
}
Librerie .NET Core
Questa sezione contiene i seguenti argomenti secondari:
- riflessione
- serializzazione
- Astrazione del tempo
- miglioramenti di UTF8
- Metodi per lavorare con la casualità
- tipi incentrati sulle prestazioni
- System.Numerics e System.Runtime.Intrinsics
- Convalida Dei Dati
- metriche
- Crittografia
- Rete
- Metodi ZipFile basati su Stream
Riflessione
puntatori a funzione sono stati introdotti in .NET 5, tuttavia, il supporto corrispondente per la reflection non è stato aggiunto in quel momento. Quando si usano rispettivamente typeof
o reflection su un puntatore a funzione, typeof(delegate*<void>())
o FieldInfo.FieldType
, viene restituito un IntPtr. A partire da .NET 8, viene restituito un oggetto System.Type. Questo tipo fornisce l'accesso ai metadati del puntatore a funzione, incluse le convenzioni di chiamata, il tipo restituito e i parametri.
Nota
Un'istanza del puntatore di funzione, ovvero un indirizzo fisico di una funzione, continua a essere rappresentata come un IntPtr. Solo il tipo di riflessione è stato modificato.
La nuova funzionalità è attualmente implementata solo nel runtime CoreCLR e MetadataLoadContext.
Sono state aggiunte nuove API a System.Type, ad esempio IsFunctionPointere a System.Reflection.PropertyInfo, System.Reflection.FieldInfoe System.Reflection.ParameterInfo. Il codice seguente illustra come usare alcune delle nuove API per la riflessione.
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}");
}
}
}
}
L'esempio precedente produce l'output seguente:
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
Serializzazione
Sono stati apportati molti miglioramenti alla funzionalità di serializzazione e deserializzazione di System.Text.Json in .NET 8. Ad esempio, è possibile personalizzare la gestione delle proprietà JSON che non si trovano in POCO.
Le sezioni seguenti descrivono altri miglioramenti della serializzazione:
- supporto predefinito per tipi aggiuntivi
- generatore di origine
- gerarchie dell'interfaccia
- Politiche di denominazione
- proprietà di sola lettura
- Disabilitare le impostazioni predefinite basate su reflection
- nuovi metodi dell'API JsonNode
- membri non pubblici
- API di deserializzazione di streaming
- metodo di estensione WithAddedModifier
- nuovi overload JsonContent.Create
- Blocca un'istanza di JsonSerializerOptions
Per ulteriori informazioni sulla serializzazione JSON in generale, consultare serializzazione e deserializzazione JSON in .NET.
Supporto predefinito per tipi aggiuntivi
Il serializzatore offre il supporto predefinito per i seguenti tipi aggiuntivi.
Half, Int128e UInt128 tipi numerici.
Console.WriteLine(JsonSerializer.Serialize( [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ] )); // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
Memory<T> e valori di ReadOnlyMemory<T>. I valori
byte
vengono serializzati in stringhe Base64 e gli altri tipi in array JSON.JsonSerializer.Serialize<ReadOnlyMemory<byte>>(new byte[] { 1, 2, 3 }); // "AQID" JsonSerializer.Serialize<Memory<int>>(new int[] { 1, 2, 3 }); // [1,2,3]
Generatore di codice sorgente
.NET 8 include miglioramenti del generatore di origine System.Text.Json che consentono di rendere l'esperienza di Native AOT in linea con il serializzatore basato su reflection . Per esempio:
Il generatore di origine supporta ora la serializzazione dei tipi con proprietà
required
einit
. Questi due sono entrambi già supportati nella serializzazione basata su reflection.Miglioramento della formattazione del codice generato dall'origine.
JsonSourceGenerationOptionsAttribute parità di funzionalità con JsonSerializerOptions. Per altre informazioni, vedere Specificare le opzioni (generazione di origine).
Diagnostica aggiuntiva ( ad esempio SYSLIB1034 e SYSLIB1039).
Non includere tipi di proprietà ignorate o inaccessibili.
Supporto per l'annidamento di dichiarazioni di
JsonSerializerContext
all'interno di tipi arbitrari.Supporto per i tipi generati dal compilatore o incomprensibili in scenari di generazione di codice a tipizzazione debole. Poiché i tipi generati dal compilatore non possono essere specificati in modo esplicito dal generatore di origine, System.Text.Json ora esegue la risoluzione predecessore più vicina in fase di esecuzione. Questa risoluzione determina il supertipo più appropriato con cui serializzare il valore.
Nuovo tipo di convertitore
JsonStringEnumConverter<TEnum>
. La classe JsonStringEnumConverter esistente non è supportata in AOT nativo. È possibile annotare i tipi di enumerazione come indicato di seguito:[JsonConverter(typeof(JsonStringEnumConverter<MyEnum>))] public enum MyEnum { Value1, Value2, Value3 } [JsonSerializable(typeof(MyEnum))] public partial class MyContext : JsonSerializerContext { }
Per ulteriori informazioni, vedere serializzare i campi enum come stringhe.
La nuova proprietà
JsonConverter.Type
consente di cercare il tipo di un'istanza diJsonConverter
non generica:Dictionary<Type, JsonConverter> CreateDictionary(IEnumerable<JsonConverter> converters) => converters.Where(converter => converter.Type != null) .ToDictionary(converter => converter.Type!);
La proprietà è annullabile poiché restituisce
null
per le istanze diJsonConverterFactory
etypeof(T)
per quelle diJsonConverter<T>
.
Generatori di sorgente a catena
La classe JsonSerializerOptions include una nuova proprietà TypeInfoResolverChain che integra la proprietà TypeInfoResolver esistente. Queste proprietà vengono utilizzate nella personalizzazione del contratto per il collegamento in sequenza dei generatori di codice sorgente. L'aggiunta della nuova proprietà significa che non è necessario specificare tutti i componenti concatenati in un punto di chiamata; possono essere aggiunti successivamente. TypeInfoResolverChain consente anche di introspezionare la catena o rimuovere componenti da essa. Per ulteriori informazioni, vedere Combinare generatori di codice sorgente.
Inoltre, JsonSerializerOptions.AddContext<TContext>() è obsoleto. È stato sostituito dalle proprietà TypeInfoResolver e TypeInfoResolverChain. Per altre informazioni, vedere SYSLIB0049.
Gerarchie di interfaccia
.NET 8 aggiunge il supporto per la serializzazione delle proprietà dalle gerarchie di interfaccia.
Il codice seguente mostra un esempio in cui le proprietà dell'interfaccia implementata immediatamente e della relativa interfaccia di base vengono serializzate.
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; }
}
Criteri di denominazione
JsonNamingPolicy
include politiche di denominazione nuove per le conversioni dei nomi di proprietà snake_case
(con un carattere di sottolineatura) e kebab-case
(con un trattino). Usare questi criteri in modo analogo ai criteri di JsonNamingPolicy.CamelCase esistenti:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
JsonSerializer.Serialize(new { PropertyName = "value" }, options);
// { "property_name" : "value" }
Per altre informazioni, vedere Usare un criterio di denominazione predefinito.
Proprietà di sola lettura
È ora possibile deserializzare in campi o proprietà di sola lettura, ovvero quelli che non dispongono di una funzione di accesso set
.
Per acconsentire esplicitamente a questo supporto a livello globale, impostare una nuova opzione, PreferredObjectCreationHandling, su JsonObjectCreationHandling.Populate. Se la compatibilità è un problema, è anche possibile abilitare la funzionalità in modo più granulare inserendo l'attributo [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
su tipi specifici le cui proprietà devono essere popolate o su singole proprietà.
Si consideri ad esempio il codice seguente che deserializza in un tipo CustomerInfo
con due proprietà di sola lettura.
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"
};
}
Prima di .NET 8, i valori di input venivano ignorati e le proprietà Names
e Company
conservavano i valori predefiniti.
{"Names":[],"Company":{"Name":"N/A","PhoneNumber":"N/A"}}
I valori di input vengono ora usati per popolare le proprietà di sola lettura durante la deserializzazione.
{"Names":["John Doe"],"Company":{"Name":"Contoso","PhoneNumber":"N/A"}}
Per altre informazioni sul popolare comportamento di deserializzazione, vedere Popolare le proprietà inizializzate.
Disabilitare l'impostazione predefinita basata su reflection
Ora è possibile disabilitare, per impostazione predefinita, l'uso del serializzatore basato su reflection. Questa disabilitazione è utile per evitare il rooting accidentale dei componenti di riflessione che non sono nemmeno in uso, specialmente nelle applicazioni ridotte e Native AOT. Per disabilitare la serializzazione predefinita basata su reflection, richiedendo che un argomento JsonSerializerOptions sia passato ai metodi di serializzazione e deserializzazione JsonSerializer, è necessario impostare la proprietà MSBuild JsonSerializerIsReflectionEnabledByDefault
a false
nel file di progetto.
Usare la nuova API IsReflectionEnabledByDefault per controllare il valore dell'opzione di funzionalità. Se si è un autore della libreria che si basa su System.Text.Json, è possibile basarsi sulla proprietà per configurare le impostazioni predefinite senza eseguire accidentalmente il rooting dei componenti di reflection.
Per altre informazioni, vedere Disabilitare le impostazioni predefinite di riflessione.
Nuovi metodi dell'API JsonNode
I tipi JsonNode e System.Text.Json.Nodes.JsonArray includono i nuovi metodi seguenti.
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>();
}
Membri non pubblici
È possibile includere membri non pubblici nel contratto di serializzazione per un determinato tipo usando le annotazioni degli attributi JsonIncludeAttribute e JsonConstructorAttribute.
public static void NonPublicMembers()
{
string json = JsonSerializer.Serialize(new MyPoco(42));
Console.WriteLine(json);
// {"X":42}
JsonSerializer.Deserialize<MyPoco>(json);
}
public class MyPoco
{
[JsonConstructor]
internal MyPoco(int x) => X = x;
[JsonInclude]
internal int X { get; }
}
Per altre informazioni, vedere Usare tipi non modificabili e membri non pubblici e funzioni di accesso.
API di deserializzazione di streaming
.NET 8 include nuovi metodi di estensione per la deserializzazione di flusso IAsyncEnumerable<T>, ad esempio GetFromJsonAsAsyncEnumerable. Esistono metodi simili che restituiscono Task<TResult>, ad esempio HttpClientJsonExtensions.GetFromJsonAsync. I nuovi metodi di estensione richiamano le API di streaming e restituiscono IAsyncEnumerable<T>.
Il codice seguente illustra come usare i nuovi metodi di estensione.
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);
Metodo di estensione WithAddedModifier
Il nuovo metodo di estensione WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) consente di introdurre facilmente modifiche ai contratti di serializzazione di istanze arbitrarie di IJsonTypeInfoResolver
.
var options = new JsonSerializerOptions
{
TypeInfoResolver = MyContext.Default
.WithAddedModifier(static typeInfo =>
{
foreach (JsonPropertyInfo prop in typeInfo.Properties)
{
prop.Name = prop.Name.ToUpperInvariant();
}
})
};
I nuovi overload di JsonContent.Create
È ora possibile creare istanze di JsonContent usando contratti trim-safe o generati dall'origine. I nuovi metodi sono:
- JsonContent.Create(Object, JsonTypeInfo, MediaTypeHeaderValue)
- JsonContent.Create<T>(T, JsonTypeInfo<T>, MediaTypeHeaderValue)
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
{
}
Bloccare un'istanza di JsonSerializerOptions
I nuovi metodi seguenti consentono di controllare quando un'istanza di JsonSerializerOptions è bloccata:
JsonSerializerOptions.MakeReadOnly()
Questo overload è progettato per essere tagliato e genererà quindi un'eccezione nei casi in cui l'istanza delle opzioni non è stata configurata con un sistema di risoluzione.
JsonSerializerOptions.MakeReadOnly(Boolean)
Se passi
true
a questo overload, l'istanza delle opzioni viene popolata con il resolver di reflection predefinito, se questo è mancante. Questo metodo è contrassegnatoRequiresUnreferenceCode
/RequiresDynamicCode
ed è pertanto non adatto per le applicazioni AOT native.
La nuova proprietà IsReadOnly consente di verificare se l'istanza delle opzioni è bloccata.
Astrazione temporale
La nuova classe TimeProvider e l'interfaccia ITimer aggiungono funzionalità di astrazione temporale, che consente di simulare il tempo negli scenari di test. Inoltre, è possibile usare l'astrazione temporale per simulare Task operazioni che si basano sull'avanzamento del tempo usando Task.Delay e Task.WaitAsync. L'astrazione temporale supporta le operazioni temporali essenziali seguenti:
- Recuperare l'ora locale e UTC
- Ottenere un timestamp per misurare le prestazioni
- Creare un timer
Il frammento di codice seguente mostra alcuni esempi di utilizzo.
// 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);
}
Miglioramenti di UTF8
Se si vuole abilitare la scrittura di una rappresentazione simile a una stringa del proprio tipo in un segmento di destinazione, implementare l'interfaccia IUtf8SpanFormattable nel proprio tipo. Questa nuova interfaccia è strettamente correlata a ISpanFormattable, ma ha come destinazione UTF8 e Span<byte>
anziché UTF16 e Span<char>
.
IUtf8SpanFormattable è stato implementato in tutti i tipi primitivi (più altri), con la stessa logica condivisa indipendentemente dal fatto che sia destinato a string
, Span<char>
o Span<byte>
. Ha il supporto completo per tutti i formati (incluso il nuovo identificatore binario "B") e tutte le culture. Ciò significa che è ora possibile formattare direttamente in UTF8 da Byte
, Complex
, Char
, DateOnly
, DateTime
, DateTimeOffset
, Decimal
, Double
, Guid
, Half
, IPAddress
, IPNetwork
, Int16
, Int32
, Int64
, Int128
, IntPtr
, NFloat
, SByte
, Single
, Rune
, TimeOnly
, TimeSpan
, UInt16
, UInt32
, UInt64
, UInt128
, UIntPtr
e Version
.
I nuovi metodi di Utf8.TryWrite forniscono una controparte basata su UTF8 ai metodi MemoryExtensions.TryWrite esistenti, basati su UTF16. È possibile usare la sintassi di stringa interpolata per formattare un'espressione complessa direttamente in un intervallo di byte UTF8, ad esempio:
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);
L'implementazione riconosce IUtf8SpanFormattable sui valori di formato e usa le relative implementazioni per scrivere le rappresentazioni UTF8 direttamente nell'intervallo di destinazione.
L'implementazione usa anche il nuovo metodo Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32), che insieme alla controparte Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32), supporta la codifica e la decodifica in un intervallo di destinazione. Se l'intervallo non è sufficiente per contenere lo stato risultante, i metodi restituiscono false
anziché generare un'eccezione.
Metodi per l'uso della casualità
I tipi System.Random e System.Security.Cryptography.RandomNumberGenerator introducono due nuovi metodi per lavorare con la casualità.
OttieniElementi<T>()
I nuovi metodi System.Random.GetItems e System.Security.Cryptography.RandomNumberGenerator.GetItems consentono di scegliere in modo casuale un numero specificato di elementi da un set di input. Nell'esempio seguente viene illustrato come usare System.Random.GetItems<T>()
(nell'istanza fornita dalla proprietà Random.Shared) per inserire in modo casuale 31 elementi in una matrice. Questo esempio può essere usato in un gioco di "Simon" in cui i giocatori devono ricordare una sequenza di pulsanti colorati.
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>()
I nuovi metodi Random.Shuffle e RandomNumberGenerator.Shuffle<T>(Span<T>) consentono di casualizzare l'ordine di un intervallo. Questi metodi sono utili per ridurre il bias del training nell'apprendimento automatico (quindi la prima cosa non è sempre l'addestramento e l'ultima cosa non è sempre il 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);
// ...
Tipi incentrati sulle prestazioni
.NET 8 introduce diversi nuovi tipi volti a migliorare le prestazioni delle app.
Il nuovo spazio dei nomi System.Collections.Frozen include i tipi di raccolta FrozenDictionary<TKey,TValue> e FrozenSet<T>. Questi tipi non consentono modifiche alle chiavi e ai valori dopo la creazione di una raccolta. Questo requisito consente operazioni di lettura più veloci, ad esempio
TryGetValue()
. Questi tipi sono particolarmente utili per le raccolte popolate al primo utilizzo e quindi rese persistenti per la durata di un servizio di lunga durata, ad esempio:private static readonly FrozenDictionary<string, bool> s_configurationData = LoadConfigurationData().ToFrozenDictionary(); // ... if (s_configurationData.TryGetValue(key, out bool setting) && setting) { Process(); }
Metodi come MemoryExtensions.IndexOfAny cercano la prima occorrenza di qualsiasi valore nella raccolta passata. Il nuovo tipo di System.Buffers.SearchValues<T> è progettato per essere passato a tali metodi. Analogamente, .NET 8 aggiunge nuovi sovraccarichi di metodi come MemoryExtensions.IndexOfAny che accettano un'istanza del nuovo tipo. Quando si crea un'istanza di SearchValues<T>, tutti i dati necessari per ottimizzare le ricerche successive vengono derivati in quel momento, ovvero il lavoro viene eseguito in anticipo.
Il nuovo tipo di System.Text.CompositeFormat è utile per ottimizzare le stringhe di formato non note in fase di compilazione, ad esempio se la stringa di formato viene caricata da un file di risorse. Un po' di tempo aggiuntivo viene impiegato prima per eseguire operazioni come l'analisi della stringa, ma evita di ripetere il lavoro su ogni uso.
private static readonly CompositeFormat s_rangeMessage = CompositeFormat.Parse(LoadRangeMessageResource()); // ... static string GetMessage(int min, int max) => string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
I nuovi tipi di System.IO.Hashing.XxHash3 e System.IO.Hashing.XxHash128 forniscono implementazioni degli algoritmi hash XXH3 e XXH128 veloci.
System.Numerics e System.Runtime.Intrinsics
Questa sezione illustra i miglioramenti apportati agli spazi dei nomi System.Numerics e System.Runtime.Intrinsics.
-
Vector256<T>, Matrix3x2e Matrix4x4 hanno migliorato l'accelerazione hardware in .NET 8. Ad esempio, Vector256<T> è stato reinterpretato per operare internamente come operazioni
2x Vector128<T>
, se possibile. Ciò consente l'accelerazione parziale di alcune funzioni quandoVector128.IsHardwareAccelerated == true
maVector256.IsHardwareAccelerated == false
, ad esempio in Arm64. - Gli intrinseci hardware sono ora annotati con l'attributo
ConstExpected
. Ciò garantisce che gli utenti siano consapevoli quando l'hardware sottostante prevede una costante e pertanto quando un valore non costante potrebbe compromettere in modo imprevisto le prestazioni. - L'API Lerp(TSelf, TSelf, TSelf)
Lerp
è stata aggiunta a IFloatingPointIeee754<TSelf> e quindi afloat
(Single),double
(Double) e Half. Questa API consente di eseguire un'interpolazione lineare tra due valori in modo efficiente e corretto.
Vector512 e AVX-512
Supporto SIMD esteso di .NET Core 3.0 per includere le API intrinseche hardware specifiche della piattaforma per x86/x64. .NET 5 ha aggiunto il supporto per Arm64 e .NET 7 ha aggiunto gli intrinseci hardware multipiattaforma. .NET 8 supporta ulteriormente SIMD introducendo Vector512<T> e supporto per Intel Advanced Vector Extensions 512 (AVX-512) istruzioni.
In particolare, .NET 8 include il supporto per le funzionalità chiave seguenti di AVX-512:
- Operazioni vettoriali a 512 bit
- Altri 16 registri SIMD
- Istruzioni aggiuntive disponibili per vettori a 128 bit, a 256 bit e a 512 bit
Se si dispone di hardware che supporta la funzionalità, Vector512.IsHardwareAccelerated ora segnala true
.
.NET 8 aggiunge anche diverse classi specifiche della piattaforma nello spazio dei nomi System.Runtime.Intrinsics.X86:
- Avx512F (fondamentale)
- Avx512BW (byte e parola)
- Avx512CD (rilevamento dei conflitti)
- Avx512DQ (doubleword e quadword)
- Avx512Vbmi (istruzioni di manipolazione dei byte vettoriali)
Queste classi seguono la stessa forma generale delle altre architetture del set di istruzioni (ISA) in quanto espongono una proprietà IsSupported e una classe Avx512F.X64 annidata per istruzioni disponibili solo per i processi a 64 bit. Inoltre, ogni classe ha una classe Avx512F.VL annidata che espone le estensioni Avx512VL
(lunghezza vettore) per il set di istruzioni corrispondente.
Anche se non utilizzi in modo esplicito istruzioni specifiche per Vector512
o Avx512F
nel tuo codice, è probabile che trarrai comunque vantaggio dal nuovo supporto AVX-512. JIT può sfruttare i registri e le istruzioni aggiuntivi in modo implicito quando si usano Vector128<T> o Vector256<T>. La libreria di classi di base usa questi intrinseci hardware internamente nella maggior parte delle operazioni esposte da Span<T> e ReadOnlySpan<T> e in molte API matematiche esposte per i tipi primitivi.
Convalida dei dati
Lo spazio dei nomi System.ComponentModel.DataAnnotations include nuovi attributi di convalida dei dati destinati agli scenari di convalida nei servizi nativi del cloud. Sebbene i validator di DataAnnotations
preesistenti siano orientati alla tipica convalida dell'immissione dei dati dell'interfaccia utente, ad esempio i campi di un modulo, i nuovi attributi sono progettati per convalidare i dati di immissione non utente, ad esempio opzioni di configurazione. Oltre ai nuovi attributi, sono state aggiunte nuove proprietà al tipo di RangeAttribute.
Nuova API | Descrizione |
---|---|
RangeAttribute.MinimumIsExclusive RangeAttribute.MaximumIsExclusive |
Specifica se i limiti sono inclusi nell'intervallo consentito. |
System.ComponentModel.DataAnnotations.LengthAttribute | Specifica limiti inferiori e superiori per stringhe o raccolte. Ad esempio, [Length(10, 20)] richiede almeno 10 elementi e al massimo 20 elementi in una raccolta. |
System.ComponentModel.DataAnnotations.Base64StringAttribute | Convalida che una stringa sia una rappresentazione Base64 valida. |
System.ComponentModel.DataAnnotations.AllowedValuesAttribute System.ComponentModel.DataAnnotations.DeniedValuesAttribute |
Specificare gli elenchi consentiti e gli elenchi di rifiuto, rispettivamente. Ad esempio, [AllowedValues("apple", "banana", "mango")] . |
Metriche
Le nuove API consentono di allegare tag di coppia chiave-valore a Meter e Instrument oggetti durante la creazione. Gli aggregatori delle misurazioni delle metriche pubblicate possono usare i tag per differenziare i valori aggregati.
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);
Le nuove API includono:
- MeterOptions
- Meter(MeterOptions)
- CreateCounter<T>(String, String, String, IEnumerable<KeyValuePair<String,Object>>)
Crittografia
.NET 8 aggiunge il supporto per le primitive di hashing SHA-3. SHA-3 è attualmente supportato da Linux con OpenSSL 1.1.1 o versione successiva e Windows 11 Build 25324 o versione successiva. Le API in cui SHA-2 è ora disponibile offrono un complimento SHA-3. Sono inclusi SHA3_256
, SHA3_384
e SHA3_512
per l'hashing; HMACSHA3_256
, HMACSHA3_384
e HMACSHA3_512
per HMAC; HashAlgorithmName.SHA3_256
, HashAlgorithmName.SHA3_384
e HashAlgorithmName.SHA3_512
per l'hashing in cui l'algoritmo è configurabile; e RSAEncryptionPadding.OaepSHA3_256
, RSAEncryptionPadding.OaepSHA3_384
e RSAEncryptionPadding.OaepSHA3_512
per la crittografia OAEP RSA.
L'esempio seguente illustra come usare le API, inclusa la proprietà SHA3_256.IsSupported
per determinare se la piattaforma supporta 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
{
// ...
}
Il supporto sha-3 è attualmente destinato al supporto delle primitive crittografiche. Le costruzioni e i protocolli di livello superiore non dovrebbero supportare completamente SHA-3 inizialmente. Questi protocolli includono certificati X.509, SignedXmle COSE.
Rete di contatti
Supporto per il proxy HTTPS
Fino ad ora, i tipi di proxy che HttpClient supportavano tutti consentivano a un "man-in-the-middle" di vedere il sito a cui si connette il client, anche per gli URI HTTPS. HttpClient ora supporta proxy HTTPS, che crea un canale crittografato tra il client e il proxy in modo che tutte le richieste possano essere gestite con privacy completa.
Per abilitare il proxy HTTPS, impostare la variabile di ambiente all_proxy
oppure usare la classe WebProxy per controllare il proxy a livello di codice.
Unix: export all_proxy=https://x.x.x.x:3218
Windows: set all_proxy=https://x.x.x.x:3218
È anche possibile usare la classe WebProxy per controllare il proxy a livello di codice.
Metodi di ZipFile basati su flusso
.NET 8 include nuovi overload di ZipFile.CreateFromDirectory che consentono di raccogliere tutti i file inclusi in una directory e comprimerli, quindi archiviare il file ZIP risultante nel flusso fornito. Analogamente, i nuovi overload di ZipFile.ExtractToDirectory consentono di fornire un flusso contenente un file compresso ed estrarne il contenuto nel file system. Questi sono i nuovi sovraccarichi:
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) { }
}
Queste nuove API possono essere utili quando lo spazio su disco è vincolato, perché evitano di dover usare il disco come passaggio intermedio.
Librerie di estensioni
Questa sezione contiene i seguenti argomenti secondari:
- Opzioni di convalida
- costruttori LoggerMessageAttribute
- metriche delle estensioni
- servizi del ciclo di vita ospitati
- servizi di inserimento delle dipendenze con chiave
- System.Numerics.Tensors.TensorPrimitives
Servizi DI con chiavi
I servizi DI (Dependency Injection) associati a chiave forniscono un metodo per la registrazione e il recupero dei servizi DI utilizzando chiavi. Usando le chiavi, è possibile definire l'ambito della registrazione e dell'utilizzo dei servizi. Ecco alcune delle nuove API:
- Interfaccia IKeyedServiceProvider.
- Attributo ServiceKeyAttribute, che può essere usato per inserire la chiave usata per la registrazione/risoluzione nel costruttore.
- Attributo FromKeyedServicesAttribute, che può essere usato nei parametri del costruttore del servizio per specificare il servizio con chiave da usare.
- Vari nuovi metodi di estensione per IServiceCollection per supportare i servizi con chiave, ad esempio ServiceCollectionServiceExtensions.AddKeyedScoped.
- Implementazione ServiceProvider di IKeyedServiceProvider.
L'esempio seguente illustra come usare i servizi DI con chiave.
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.";
}
Per altre informazioni, vedere dotnet/runtime#64427.
Servizi del ciclo di vita ospitati
I servizi ospitati hanno ora più opzioni per l'esecuzione durante il ciclo di vita dell'applicazione.
IHostedService ha fornito StartAsync
e StopAsync
, e ora IHostedLifecycleService fornisce questi metodi aggiuntivi:
- StartingAsync(CancellationToken)
- StartedAsync(CancellationToken)
- StoppingAsync(CancellationToken)
- StoppedAsync(CancellationToken)
Questi metodi vengono eseguiti rispettivamente prima e dopo i punti esistenti.
L'esempio seguente illustra come usare le nuove API.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
internal class HostedLifecycleServices
{
public async static void RunIt()
{
IHostBuilder hostBuilder = new HostBuilder();
hostBuilder.ConfigureServices(services =>
{
services.AddHostedService<MyService>();
});
using (IHost host = hostBuilder.Build())
{
await host.StartAsync();
}
}
public class MyService : IHostedLifecycleService
{
public Task StartingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StartAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StartedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StoppedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StoppingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
}
}
Per altre informazioni, vedere dotnet/runtime#86511.
Convalida delle opzioni
Generatore di codice sorgente
Per ridurre il sovraccarico di avvio e migliorare il set di funzionalità di convalida, è stato introdotto un generatore di codice sorgente che implementa la logica di convalida. Il codice seguente illustra modelli di esempio e classi di validator.
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>
{
}
Se l'app utilizza l'iniezione delle dipendenze, è possibile iniettare la convalida nel modo illustrato nel codice di esempio seguente.
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>();
Tipo ValidateOptionsResultBuilder
.NET 8 introduce il tipo di ValidateOptionsResultBuilder per facilitare la creazione di un oggetto ValidateOptionsResult. È importante notare che questo strumento consente l'accumulo di più errori. In precedenza, la creazione dell'oggetto ValidateOptionsResult necessario per implementare IValidateOptions<TOptions>.Validate(String, TOptions) era difficile e talvolta generava errori di convalida a più livelli. Se si sono verificati più errori, il processo di convalida viene spesso arrestato al primo errore.
Il frammento di codice seguente illustra un esempio di utilizzo di 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();
Costruttori di LoggerMessageAttribute
LoggerMessageAttribute ora offre overload aggiuntivi del costruttore. In precedenza era necessario scegliere il costruttore senza parametri o il costruttore che richiedeva tutti i parametri (ID evento, livello di log e messaggio). I nuovi overload offrono maggiore flessibilità per specificare i parametri necessari con codice ridotto. Se non si specifica un ID evento, il sistema ne genera uno automaticamente.
public LoggerMessageAttribute(LogLevel level, string message);
public LoggerMessageAttribute(LogLevel level);
public LoggerMessageAttribute(string message);
Metriche delle estensioni
Interfaccia IMeterFactory
È possibile registrare la nuova interfaccia IMeterFactory nei contenitori di inserimento delle dipendenze e usarla per creare oggetti Meter in modo isolato.
Registrare il IMeterFactory nel contenitore DI usando l'implementazione predefinita della fabbrica dei misuratori.
// 'services' is the DI IServiceCollection.
services.AddMetrics();
I consumatori possono quindi ottenere la fabbrica del contatore e usarla per creare un nuovo oggetto Meter.
IMeterFactory meterFactory = serviceProvider.GetRequiredService<IMeterFactory>();
MeterOptions options = new MeterOptions("MeterName")
{
Version = "version",
};
Meter meter = meterFactory.Create(options);
Classe<T> MetricCollector
La nuova classe MetricCollector<T> consente di registrare le misurazioni delle metriche insieme ai timestamp. Inoltre, la classe offre la flessibilità necessaria per usare un provider di tempo di propria scelta per una generazione di timestamp accurata.
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
L'aggiornamento del System.Numerics.Tensors pacchetto NuGet include le API nel nuovo tipo System.Numerics.Tensors.TensorPrimitives che aggiungono supporto per le operazioni tensoriali. Le primitive tensor ottimizzano carichi di lavoro a elevato utilizzo di dati, come quelli di intelligenza artificiale e Machine Learning.
I carichi di lavoro di intelligenza artificiale, ad esempio la ricerca semantica e la generazione avanzata (RAG) estendono le funzionalità del linguaggio naturale di modelli linguistici di grandi dimensioni, ad esempio ChatGPT, aumentando le richieste con i dati pertinenti. Per questi carichi di lavoro, le operazioni sui vettori, ad esempio somiglianza del coseno per trovare i dati più rilevanti per rispondere a una domanda, sono fondamentali. Il tipo TensorPrimitives fornisce API per le operazioni vettoriali.
Per altre informazioni, vedere il post di blog Announcing .NET 8 RC 2 (Annuncio di .NET 8 RC 2 2).
Supporto AOT nativo
L'opzione per pubblicare come AOT nativo è stata introdotta per la prima volta in .NET 7. La pubblicazione di un'app con AOT nativo crea una versione completamente autonoma dell'app che non richiede un runtime. Tutto è incluso in un singolo file. .NET 8 offre i miglioramenti seguenti alla pubblicazione AOT nativa:
Aggiunge il supporto per le architetture x64 e Arm64 in macOS.
Riduce le dimensioni delle app AOT native in Linux fino a 50%. La tabella seguente illustra le dimensioni di un'app "Hello World" pubblicata con AOT nativo che include l'intero runtime .NET in .NET 7 rispetto a .NET 8:
Sistema operativo .NET 7 .NET 8 Linux x64 (con -p:StripSymbols=true
)3,76 MB 1,84 MB Windows x64 2,85 MB 1,77 MB Consente di specificare una preferenza di ottimizzazione: dimensioni o velocità. Per impostazione predefinita, il compilatore sceglie di generare codice veloce tenendo conto delle dimensioni dell'applicazione. Tuttavia, è possibile usare la proprietà
<OptimizationPreference>
MSBuild per ottimizzare in modo specifico per uno o l'altro. Per ulteriori informazioni, vedere Ottimizzazione delle distribuzioni AOT.
Rivolgersi a piattaforme simili a iOS con AOT nativo
.NET 8 avvia il lavoro per abilitare il supporto AOT nativo per le piattaforme simili a iOS. È ora possibile compilare ed eseguire applicazioni .NET iOS e .NET MAUI con Native AOT nelle piattaforme seguenti:
ios
iossimulator
maccatalyst
tvos
tvossimulator
Il test preliminare mostra che le dimensioni delle app sul disco diminuiscono di circa 35% per le app iOS .NET che usano AOT nativo anziché Mono. Le dimensioni delle app su disco per le app iOS .NET MAUI diminuiscono fino a 50%. Inoltre, il tempo di avvio è anche più veloce. Le app iOS .NET hanno circa 28% tempi di avvio più veloci, mentre le app .NET MAUI iOS hanno circa 50% prestazioni di avvio migliori rispetto a Mono. Il supporto di .NET 8 è sperimentale e solo il primo passaggio per la funzionalità nel suo complesso. Per ulteriori informazioni, vedere il post di blog sui miglioramenti delle prestazioni di .NET 8 in .NET MAUI .
Il supporto AOT nativo è disponibile come funzionalità di consenso esplicito destinata alla distribuzione di app; Mono è ancora il runtime predefinito per lo sviluppo e la distribuzione di app. Per compilare ed eseguire un'applicazione MAUI .NET con AOT nativo in un dispositivo iOS, usare dotnet workload install maui
per installare il carico di lavoro MAUI .NET e dotnet new maui -n HelloMaui
per creare l'app. Impostare quindi la proprietà MSBuild PublishAot
su true
nel file di progetto.
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
Quando imposti la proprietà richiesta ed esegui dotnet publish
come illustrato nell'esempio seguente, l'app verrà distribuita usando AOT nativo.
dotnet publish -f net8.0-ios -c Release -r ios-arm64 /t:Run
Limitazioni
Non tutte le funzionalità di iOS sono compatibili con Native AOT. Analogamente, non tutte le librerie comunemente usate in iOS sono compatibili con NativeAOT. Oltre alle limitazioni di esistenti della distribuzione AOT nativa, l'elenco seguente mostra alcune delle altre limitazioni per le piattaforme simili a iOS:
- L'uso di AOT nativo è abilitato solo durante la distribuzione dell'app (
dotnet publish
). - Il debug del codice gestito è supportato solo con Mono.
- La compatibilità con il framework MAUI .NET è limitata.
Compilazione AOT per le app Android
Per ridurre le dimensioni delle app, le app .NET e .NET MAUI destinate ad Android usano profilato modalità di compilazione anticipata (AOT) quando sono compilate in modalità release. La compilazione AOT profilata influisce su un numero inferiore di metodi rispetto alla normale compilazione AOT. .NET 8 introduce la proprietà <AndroidStripILAfterAOT>
che consente di acconsentire esplicitamente a un'ulteriore compilazione AOT per le app Android per ridurre ulteriormente le dimensioni delle app.
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
</PropertyGroup>
Per impostazione predefinita, l'impostazione di AndroidStripILAfterAOT
su true
sostituisce l'impostazione AndroidEnableProfiledAot
predefinita, consentendo (quasi) di tagliare tutti i metodi compilati da AOT. È anche possibile usare insieme la profilatura AOT e la rimozione di IL impostando in modo esplicito entrambe le proprietà su true
:
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>
App di Windows compilate per piattaforme incrociate
Quando si compilano app destinate a Windows in piattaforme non Windows, l'eseguibile risultante viene ora aggiornato con tutte le risorse Win32 specificate, ad esempio icona dell'applicazione, manifesto, informazioni sulla versione.
In precedenza, le applicazioni dovevano essere compilate in Windows per avere tali risorse. La correzione di questo divario nel supporto per la costruzione incrociata è stata una richiesta comune, poiché rappresentava un problema significativo che influisce sulla complessità dell'infrastruttura e sull'utilizzo delle risorse.