Novità di .NET 9

Informazioni sulle nuove funzionalità di .NET 9 e collegamenti ad altre informazioni sulla documentazione.

.NET 9, il successore di .NET 8, pone particolare attenzione sulle app native del cloud e sulle prestazioni. Sarà supportato per 18 mesi come versione di supporto a termine standard. È possibile scaricare .NET 9 qui.

Novità di .NET 9, il team di progettazione pubblica gli aggiornamenti di anteprima di .NET 9 in Discussioni GitHub. Questo è un ottimo posto per porre domande e fornire commenti e suggerimenti sulla versione.

Questo articolo è stato aggiornato per .NET 9 Preview 2. Le sezioni seguenti descrivono gli aggiornamenti apportati alle principali librerie .NET in .NET 9.

Runtime .NET

Serializzazione

In System.Text.Json.NET 9 sono disponibili nuove opzioni per la serializzazione di JSON e un nuovo singleton che semplifica la serializzazione usando le impostazioni predefinite Web.

Opzioni di rientro

JsonSerializerOptions include nuove proprietà che consentono di personalizzare il carattere di rientro e le dimensioni del rientro del codice JSON scritto.

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    IndentCharacter = '\t',
    IndentSize = 2,
};

string json = JsonSerializer.Serialize(
    new { Value = 1 },
    options
    );
Console.WriteLine(json);
//{
//                "Value": 1
//}

Opzioni Web predefinite

Se si vuole serializzare con le opzioni predefinite usate da ASP.NET Core per le app Web, usare il nuovo singleton JsonSerializerOptions.Web.

string webJson = JsonSerializer.Serialize(
    new { SomeValue = 42 },
    JsonSerializerOptions.Web // Defaults to camelCase naming policy.
    );
Console.WriteLine(webJson);
// {"someValue":42}

LINQ

Sono stati introdotti nuovi metodi CountBy e AggregateBy. Questi metodi consentono di aggregare lo stato per chiave senza dover allocare raggruppamenti intermedi tramite GroupBy.

CountBy consente di calcolare rapidamente la frequenza di ogni chiave. Nell'esempio seguente viene trovata la parola che si verifica più frequentemente in una stringa di testo.

string sourceText = """
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    Sed non risus. Suspendisse lectus tortor, dignissim sit amet, 
    adipiscing nec, ultricies sed, dolor. Cras elementum ultrices amet diam.
""";

// Find the most frequent word in the text.
KeyValuePair<string, int> mostFrequentWord = sourceText
    .Split(new char[] { ' ', '.', ',' }, StringSplitOptions.RemoveEmptyEntries)
    .Select(word => word.ToLowerInvariant())
    .CountBy(word => word)
    .MaxBy(pair => pair.Value);

Console.WriteLine(mostFrequentWord.Key); // amet

AggregateBy consente di implementare flussi di lavoro più generici. Nell'esempio seguente viene illustrato come calcolare i punteggi associati a una determinata chiave.

(string id, int score)[] data =
    [
        ("0", 42),
        ("1", 5),
        ("2", 4),
        ("1", 10),
        ("0", 25),
    ];

var aggregatedData =
    data.AggregateBy(
        keySelector: entry => entry.id,
        seed: 0,
        (totalScore, curr) => totalScore + curr.score
        );

foreach (var item in aggregatedData)
{
    Console.WriteLine(item);
}
//(0, 67)
//(1, 15)
//(2, 4)

Index<TSource>(IEnumerable<TSource>) consente di estrarre rapidamente l'indice implicito di un valore enumerabile. È ora possibile scrivere porzioni di codice come il frammento di codice seguente per indicizzare automaticamente gli elementi in una raccolta.

IEnumerable<string> lines2 = File.ReadAllLines("output.txt");
foreach ((int index, string line) in lines2.Index())
{
    Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}

Raccolte

Il PriorityQueue<TElement,TPriority> tipo di raccolta nello System.Collections.Generic spazio dei nomi include un nuovo Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) metodo che è possibile usare per aggiornare la priorità di un elemento nella coda.

Metodo PriorityQueue.Remove()

.NET 6 ha introdotto la raccolta PriorityQueue<TElement,TPriority>, che fornisce un'implementazione semplice e veloce dell'heap di matrice. Un problema con gli heap di matrice in generale è che non supportano gli aggiornamenti prioritari, che li rende proibitivi per l'uso in algoritmi come varianti dell'algoritmo di Dijkstra.

Anche se non è possibile implementare aggiornamenti efficienti $O(\log n)$ nella raccolta esistente, il nuovo metodo PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) consente di emulare gli aggiornamenti prioritari (anche se in fase di $O(n)$):

public static void UpdatePriority<TElement, TPriority>(
    this PriorityQueue<TElement, TPriority> queue,
    TElement element,
    TPriority priority
    )
{
    // Scan the heap for entries matching the current element.
    queue.Remove(element, out _, out _);
    // Re-insert the entry with the new priority.
    queue.Enqueue(element, priority);
}

Questo metodo sblocca gli utenti che vogliono implementare algoritmi a grafo in contesti in cui le prestazioni asintotiche non sono un blocco. (Tali contesti includono l'istruzione e la creazione di prototipi.) Ecco, ad esempio, un'implementazione di toy dell'algoritmo di Dijkstra che usa la nuova API.

Crittografia

Per la crittografia, .NET 9 aggiunge un nuovo metodo hash monofase nel tipo CryptographicOperations. Aggiunge anche nuove classi che usano l'algoritmo KMAC.

Metodo CryptographicOperations.HashData()

.NET include diverse implementazioni statiche "monofase" di funzioni hash e funzioni correlate. Queste API includono SHA256.HashData e HMACSHA256.HashData. Le API monofase sono preferibili per l'uso perché possono offrire le migliori prestazioni possibili e ridurre o eliminare le allocazioni.

Se uno sviluppatore vuole fornire un'API che supporta l'hashing in cui il chiamante definisce l'algoritmo hash da usare, in genere viene eseguito accettando un argomento HashAlgorithmName. Tuttavia, l'uso di tale modello con API monofase richiederebbe il passaggio di ogni possibile HashAlgorithmName e quindi l'uso del metodo appropriato. Per risolvere il problema, .NET 9 introduce l'API CryptographicOperations.HashData. Questa API consente di produrre un hash o HMAC su un input monofase in cui l'algoritmo usato è determinato da un oggetto HashAlgorithmName.

static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data)
{
    byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data);
    ProcessHash(hash);
}

Algoritmo KMAC

.NET 9 fornisce l'algoritmo KMAC come specificato da NIST SP-800-185. KECCAK Message Authentication Code (KMAC) è una funzione pseudorandoma e una funzione hash con chiave basata su KECCAK.

Le nuove classi seguenti usano l'algoritmo KMAC. Usare le istanze per accumulare dati per produrre un MAC o usare il metodo statico HashData per un input monofase.

KmAC è disponibile in Linux con OpenSSL 3.0 o versione successiva e in Windows 11 Build 26016 o versione successiva. È possibile usare la proprietà statica IsSupported per determinare se la piattaforma supporta l'algoritmo desiderato.

if (Kmac128.IsSupported)
{
    byte[] key = GetKmacKey();
    byte[] input = GetInputToMac();
    byte[] mac = Kmac128.HashData(key, input, outputLength: 32);
}
else
{
    // Handle scenario where KMAC isn't available.
}

Riflessione

Nelle versioni di .NET Core e .NET 5-8 il supporto per la compilazione di un assembly e l'emissione di metadati di reflection per i tipi creati dinamicamente era limitato a un oggetto eseguibile AssemblyBuilder. La mancanza di supporto per il salvataggio di un assembly è spesso un blocco per i clienti che eseguono la migrazione da .NET Framework a .NET. .NET 9 aggiunge API pubbliche a AssemblyBuilder per salvare un assembly generato.

La nuova implementazione persistente AssemblyBuilder è indipendente dal runtime e dalla piattaforma. Per creare un'istanza persistente AssemblyBuilder, usare la nuova API AssemblyBuilder.DefinePersistedAssembly. L'API esistente AssemblyBuilder.DefineDynamicAssembly accetta il nome dell'assembly e gli attributi personalizzati facoltativi. Per usare la nuova API, passare l'assembly principale, System.Private.CoreLib, che viene usato per fare riferimento ai tipi di runtime di base. Non è disponibile alcuna opzione per AssemblyBuilderAccess. Per il momento, l'implementazione persistente AssemblyBuilder supporta solo il salvataggio, non l'esecuzione. Dopo aver creato un'istanza dell'oggetto persistente AssemblyBuilder, i passaggi successivi per la definizione di un modulo, un tipo, un metodo o un'enumerazione, la scrittura di IL e tutti gli altri utilizzi rimangono invariati. Ciò significa che è possibile usare il codice esistente System.Reflection.Emit così come è per salvare l'assembly. Il seguente codice illustra un esempio.

public void CreateAndSaveAssembly(string assemblyPath)
{
    AssemblyBuilder ab = AssemblyBuilder.DefinePersistedAssembly(
        new AssemblyName("MyAssembly"),
        typeof(object).Assembly
        );
    TypeBuilder tb = ab.DefineDynamicModule("MyModule")
        .DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);

    MethodBuilder mb = tb.DefineMethod(
        "SumMethod",
        MethodAttributes.Public | MethodAttributes.Static,
        typeof(int), [typeof(int), typeof(int)]
        );
    ILGenerator il = mb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    tb.CreateType();
    ab.Save(assemblyPath); // or could save to a Stream
}

public void UseAssembly(string assemblyPath)
{
    Assembly assembly = Assembly.LoadFrom(assemblyPath);
    Type type = assembly.GetType("MyType");
    MethodInfo method = type.GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, [5, 10]));
}

Prestazioni

.NET 9 include miglioramenti al compilatore JIT a 64 bit destinato a migliorare le prestazioni dell'app. Questi miglioramenti del compilatore includono:

La vettorizzazione arm64 è un'altra nuova funzionalità del runtime.

Ottimizzazioni dei cicli

Il miglioramento della generazione di codice per i cicli è una priorità per .NET 9 e il compilatore a 64 bit include una nuova ottimizzazione denominata estensione della variabile di induzione (IV).

Un IV è una variabile il cui valore cambia come iterazione del ciclo contenitore. Nel ciclo seguente for è i un IV: for (int i = 0; i < 10; i++). Se il compilatore può analizzare l'evoluzione del valore di un IV sulle iterazioni del ciclo, può produrre codice più efficiente per le espressioni correlate.

Si consideri l'esempio seguente che scorre una matrice:

static int Sum(int[] arr)
{
    int sum = 0;
    for (int i = 0; i < arr.Length; i++)
    {
        sum += arr[i];
    }

    return sum;
}

La variabile di indice, i, è di 4 byte. A livello di assembly, i registri a 64 bit vengono in genere usati per contenere indici di matrice su x64 e nelle versioni precedenti di .NET, il codice generato dal compilatore che viene esteso i a zero a 8 byte per l'accesso alla matrice, ma continua a trattare i come intero a 4 byte altrove. Tuttavia, l'estensione i a 8 byte richiede un'istruzione aggiuntiva su x64. Con l'estensione IV, il compilatore JIT a 64 bit ora si estende i a 8 byte durante il ciclo, omettendo l'estensione zero. Il ciclo sulle matrici è molto comune e i vantaggi di questa rimozione delle istruzioni si sommano rapidamente.

Miglioramenti dell'inlining per AOT nativo

Uno di . Gli obiettivi di NET per il compilatore JIT a 64 bit sono rimuovere il maggior numero possibile di restrizioni che impediscono l'inlining di un metodo. .NET 9 consente l'inlining degli accessi agli statici locali del thread in Windows x64, Linux x64 e Linux Arm64.

Per static i membri della classe, esiste esattamente un'istanza del membro in tutte le istanze della classe , che "condividono" il membro. Se il valore di un static membro è univoco per ogni thread, rendendo tale valore thread-local in grado di migliorare le prestazioni, perché elimina la necessità di una primitiva di concorrenza per accedere in modo sicuro al static membro dal thread contenitore.

In precedenza, gli accessi agli statici locali del thread nei programmi compilati da AOT nativo richiedevano al compilatore JIT a 64 bit di generare una chiamata al runtime per ottenere l'indirizzo di base dell'archiviazione locale del thread. A questo punto, il compilatore può inline queste chiamate, ottenendo un minor numero di istruzioni per accedere a questi dati.

Miglioramenti pgo: controlli dei tipi e cast

L'ottimizzazione PGO (Dynamic Profile Guided Optimization) abilitata per .NET 8 è abilitata per impostazione predefinita. NET 9 espande l'implementazione PGO del compilatore JIT a 64 bit per profilare più modelli di codice. Quando la compilazione a livelli è abilitata, il compilatore JIT a 64 bit inserisce già la strumentazione nel programma per profilarne il comportamento. Quando ricompila con le ottimizzazioni, il compilatore sfrutta il profilo compilato in fase di esecuzione per prendere decisioni specifiche per l'esecuzione corrente del programma. In .NET 9 il compilatore JIT a 64 bit usa i dati PGO per migliorare le prestazioni dei controlli dei tipi.

Per determinare il tipo di un oggetto è necessaria una chiamata al runtime, con una riduzione delle prestazioni. Quando è necessario controllare il tipo di un oggetto, il compilatore JIT a 64 bit genera questa chiamata per motivi di correttezza (i compilatori in genere non possono escludere alcuna possibilità, anche se sembrano improbabili). Tuttavia, se i dati PGO suggeriscono che un oggetto sia probabilmente un tipo specifico, il compilatore JIT a 64 bit ora genera un percorso rapido che controlla a buon mercato tale tipo e esegue il fallback sul percorso lento di chiamata nel runtime solo se necessario.

Vettorizzazione arm64 nelle librerie .NET

Una nuova EncodeToUtf8 implementazione sfrutta la capacità del compilatore JIT a 64 bit di generare istruzioni di caricamento/archiviazione multiregistro in Arm64. Questo comportamento consente ai programmi di elaborare blocchi di dati più grandi con un minor numero di istruzioni. Le app .NET in vari domini dovrebbero vedere miglioramenti della velocità effettiva nell'hardware Arm64 che supporta queste funzionalità. Alcuni benchmark tagliano il tempo di esecuzione di più della metà.

.NET SDK

Unit test

Questa sezione descrive gli aggiornamenti degli unit test in .NET 9: esecuzione di test in parallelo e output del test del logger del terminale.

Eseguire test in parallelo

In .NET 9 dotnet test è più completamente integrato con MSBuild. Poiché MSBuild supporta la compilazione in parallelo, è possibile eseguire test per lo stesso progetto in framework di destinazione diversi in parallelo. Per impostazione predefinita, MSBuild limita il numero di processi paralleli al numero di processori nel computer. È anche possibile impostare un limite personalizzato usando l'opzione -maxcpucount . Se si vuole rifiutare esplicitamente il parallelismo, impostare la TestTfmsInParallel proprietà MSBuild su false.

Visualizzazione del test del logger del terminale

La segnalazione dei risultati dei test per dotnet test è ora supportata direttamente nel logger del terminale MSBuild. Si ottengono report di test più completi durante l'esecuzione dei test (visualizza il nome del test in esecuzione) e dopo il completamento dei test (eventuali errori di test vengono visualizzati in modo migliore).

Per altre informazioni sul logger del terminale, vedere opzioni di compilazione dotnet.

Roll forward dello strumento .NET

Gli strumenti .NET sono app dipendenti dal framework che è possibile installare a livello globale o locale, quindi eseguire usando .NET SDK e i runtime .NET installati. Questi strumenti, come tutte le app .NET, hanno come destinazione una versione principale specifica di .NET. Per impostazione predefinita, le app non vengono eseguite nelle versioni più recenti di .NET. Gli autori di strumenti sono stati in grado di acconsentire esplicitamente all'esecuzione degli strumenti nelle versioni più recenti del runtime .NET impostando la RollForward proprietà MSBuild. Tuttavia, non tutti gli strumenti lo fanno.

Una nuova opzione per dotnet tool install consente agli utenti di decidere come eseguire gli strumenti .NET. Quando si installa uno strumento tramite dotnet tool installo quando si esegue lo strumento tramite dotnet tool run <toolname>, è possibile specificare un nuovo flag denominato --allow-roll-forward. Questa opzione configura lo strumento con la modalità Majorroll-forward . Questa modalità consente l'esecuzione dello strumento in una versione principale più recente di .NET se la versione corrispondente di .NET non è disponibile. Questa funzionalità consente agli early adopter di usare gli strumenti .NET senza dover modificare il codice da parte degli autori di strumenti.

Vedi anche