Novità delle librerie di .NET per .NET 11

Questo articolo descrive le nuove funzionalità nelle librerie di .NET per .NET 11. L'ultimo aggiornamento è stato eseguito per l'anteprima 7.

Diagnostica ed esecuzione di processi

Espansione dell'API Process

Process include un set sostanziale di nuove API che coprono scenari comuni in cui in precedenza era necessario collegare OutputDataReceived/ErrorDataReceived gli eventi manualmente o usare P/Invoke.

Utilità di esecuzione e acquisizione

Le nuove API one-shot consentono di avviare un processo e ottenere il risultato senza configurare manualmente:

// One-shot capture: stdout and stderr together, plus exit code.
ProcessTextOutput result = await Process.RunAndCaptureTextAsync(
    "git", ["status", "--porcelain"]);

Console.WriteLine(result.StandardOutput);
Console.WriteLine($"Exit code: {result.ExitStatus.ExitCode}");

Il set completo di helper include:

Missili spara e dimentica

Metodi del ciclo di vita SafeProcessHandle

SafeProcessHandle acquisisce metodi del ciclo di vita per scenari avanzati:

Controllo più preciso dell'impugnatura

Avvii sospesi e ricerca dei processi

System.Diagnostics.Process aggiunge un controllo più corretto sull'avvio e la ricerca dei processi:

  • ProcessStartInfo.StartSuspendedavvia un processo in stato sospeso in Windows e macOS. Abbinalo a SafeProcessHandle.Resume() per farlo eseguire una volta completata l'eventuale configurazione iniziale, ad esempio collegando un debugger o configurando oggetti di processo.
  • Process.TryGetProcessById(Int32, Process) restituisce false anziché generare quando non esiste alcun processo con l'ID specificato.
  • <xref:Microsoft.Win32.SafeHandles.SafeProcessHandle.Open(System.Int32)> e <xref:Microsoft.Win32.SafeHandles.SafeProcessHandle.TryOpen(System.Int32,Microsoft.Win32.SafeHandles.SafeProcessHandle@)> aprire un handle per un processo esistente in base all'ID.
var startInfo = new ProcessStartInfo("worker.exe") { StartSuspended = true };
using var process = Process.Start(startInfo)!;

// Attach diagnostics or configure job objects while the process is suspended,
// then call Resume to start execution once your setup is complete.
AttachProfiler(process);
process.SafeHandle.Resume();

Supporto di FORCE_COLOR nella console

L'output della console .NET supporta ora lo standard FORCE_COLOR oltre all'esistente supporto per NO_COLOR. Quando FORCE_COLOR è impostato, Console.IsOutputRedirected non elimina più i codici di escape ANSI. Ciò è utile quando si reindirizza tramite pipe l'output di dotnet run attraverso tee, a un visualizzatore di log CI o tramite less -R:

FORCE_COLOR=1 dotnet run | tee build.log

Adattatori di flussi di dati in memoria

Quattro nuovi tipi Stream incapsulano i comuni dati in memoria, così puoi passarli a qualsiasi API che si aspetta un Stream, senza copiarli prima in un MemoryStream:

  • ReadOnlyMemoryStream espone un oggetto ReadOnlyMemory<byte> come flusso di sola lettura.
  • WritableMemoryStream espone un oggetto scrivibile Memory<byte> come flusso a dimensione fissa.
  • ReadOnlySequenceStream espone un oggetto ReadOnlySequence<byte> (ad esempio, buffer da System.IO.Pipelines) come flusso senza appiattirlo.
  • StringStream legge un string o ReadOnlyMemory<char> come un flusso utilizzando una codifica specificata.
using System.IO;
using System.Text;

// Pass a string to an API that takes a Stream, with no intermediate byte[] needed.
using Stream config = new StringStream(yamlText, Encoding.UTF8);
var settings = ParseConfiguration(config);

// Expose an existing buffer as a read-only stream, for example as an HTTP request body.
ReadOnlyMemory<byte> payload = GetPayload();
using Stream body = new ReadOnlyMemoryStream(payload);
await httpClient.PostAsync(uri, new StreamContent(body));

ReadOnlySequenceStream è particolarmente utile con System.IO.Pipelines, perché trasmette direttamente sui segmenti di un ReadOnlySequence<byte> anziché allocare una copia contigua.

Gestione di testo, serializzazione e dati

Miglioramenti delle stringhe e dei caratteri

.NET 11 introduce miglioramenti significativi alle API di manipolazione di stringhe e caratteri, semplificando l'uso di caratteri e rune Unicode.

Supporto di Rune nei metodi String

La String classe include ora metodi che accettano Rune parametri, consentendo di cercare, sostituire e modificare le stringhe usando direttamente i valori scalari Unicode. Questi nuovi metodi includono:

Molti di questi metodi includono overload che accettano un StringComparison parametro per confronti che tengono conto delle impostazioni cultura.

Metodo Char.Equals con StringComparison

La Char struct ora include un Char.Equals(Char, StringComparison) metodo che accetta un StringComparison parametro, consentendo di confrontare i caratteri usando confronti ordinali o basati sulla cultura.

Supporto di Rune in TextInfo

La classe TextInfo ora fornisce i metodi TextInfo.ToLower(Rune) e TextInfo.ToUpper(Rune) che accettano parametri Rune, consentendo di eseguire conversioni di maiuscole/minuscole su singoli valori scalari Unicode.

StringBuilder.MoveChunks

Per trasferire il contenuto senza copiare caratteri, usare il nuovo metodo statico StringBuilder.MoveChunks(StringBuilder) , che sposta tutto il contenuto da un'origine StringBuilder a un nuovo StringBuilderoggetto . Dopo la chiamata, l'origine StringBuilder ha una lunghezza pari a 0:

var source = new StringBuilder("Hello, ");
source.Append("World!");

// MoveChunks transfers all content from source to a new StringBuilder.
// After the call, source contains no characters.
StringBuilder dest = StringBuilder.MoveChunks(source);
Console.WriteLine(dest);          // Hello, World!
Console.WriteLine(source.Length); // 0

Miglioramenti della codifica Base64

.NET 11 aggiunge nuove API e sovraccarichi al tipo Base64 esistente, fornendo supporto completo per la codifica e la decodifica in Base64. Queste aggiunte offrono prestazioni e flessibilità migliori rispetto ai metodi esistenti.

Nuove API Base64

Le nuove API supportano le operazioni di codifica e decodifica con vari formati di input e output:

Questi metodi forniscono metodi pratici di alto livello (che allocano e restituiscono matrici o stringhe) e metodi basati su intervalli di basso livello (per scenari di allocazione zero).

System.Text.Unicode ha due nuove funzionalità complementari. Utf16.IsValid(ReadOnlySpan<Char>) risponde se una sequenza è ben formata UTF-16 senza scansione due volte e Utf8.IndexOfInvalidSubsequence(ReadOnlySpan<Byte>) / Utf16.IndexOfInvalidSubsequence(ReadOnlySpan<Char>) restituisce la posizione della prima sequenza di codice in formato non corretto (o -1 per l'input valido). Insieme, questi metodi consentono ai parser, ai validator e ai serializzatori di segnalare errori precisi anziché messaggi di errore di codifica generici.

ReadOnlySpan<byte> bytes = [0xC3, 0x28]; // invalid UTF-8
int badIndex = Utf8.IndexOfInvalidSubsequence(bytes); // 0

ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (👍 emoji)
bool ok = Utf16.IsValid(chars); // true

Miglioramenti di System.Text.Json

Recupero informazioni tipo generico

Un modello comune quando si utilizzano System.Text.Json metadati di tipo consiste nel recuperare un JsonTypeInfo<T> da JsonSerializerOptions. In precedenza, era necessario eseguire manualmente il downcast dal metodo non generico GetTypeInfo(Type). I nuovi metodi generici JsonSerializerOptions.GetTypeInfo<T>() e JsonSerializerOptions.TryGetTypeInfo<T>(JsonTypeInfo<T>) restituiscono direttamente metadati fortemente tipizzati, eliminando il cast.

JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
options.MakeReadOnly();

// Before: manual downcast required
JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

// After: generic method returns the right type directly
JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

// TryGetTypeInfo variant for cases where the type may not be registered
if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
{
    // Use typeInfo
    _ = typeInfo;
}

Ciò è particolarmente utile quando si lavora con gli scenari di generazione di origine, NativeAOT e serializzazione polimorfica in cui l'accesso ai metadati dei tipi è comune.

Denominare e ignorare impostazioni predefinite

Le opzioni di denominazione e di esclusione disponibili in System.Text.Json ora includono:

  • JsonNamingPolicy.PascalCase: nuovo criterio di denominazione predefinito che converte i nomi delle proprietà in PascalCase. Unisce i criteri camelCase, snake_case e kebab-case esistenti.
  • Criterio di denominazione per membro: il nuovo System.Text.Json.Serialization.JsonNamingPolicyAttribute attributo consente di eseguire l'override dei criteri di denominazione per singole proprietà o campi, offrendo un controllo con granularità fine senza un convertitore personalizzato.
  • Condizioni di ignorare a livello di tipo: l'applicazione System.Text.Json.Serialization.JsonIgnoreAttribute a livello di classe o struct imposta il comportamento di ignorare predefinito per tutti i membri, quindi non è più necessario ripetere l'attributo in ogni proprietà nullable.
// Type-level JsonIgnore: all members use WhenWritingNull by default
// Per-member JsonNamingPolicy: EventName uses camelCase even though the
// serializer options use PascalCase
var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.PascalCase
};

var data = new EventData { EventName = "Launch", Notes = null };
string json = JsonSerializer.Serialize(data, options);
Console.WriteLine(json);
// {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased

Supporto dell'unione discriminata F#

Il serializzatore ora gestisce nativamente le unioni discriminate di F#. Le app che condividono tipi tra produttori F# e consumer C# non necessitano più di un convertitore personalizzato per le forme più comuni:

type Shape =
    | Circle of radius: float
    | Square of side: float

let json = System.Text.Json.JsonSerializer.Serialize(Circle 1.5)
// {"$type":"Circle","radius":1.5}

Utf8JsonWriter.Reset usando le opzioni

Reset ora accetta un parametro JsonWriterOptions, quindi le istanze di writer possono essere reintegrate nel pool con opzioni diverse senza allocare una nuova istanza di writer:

using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
writer.WriteStartObject();
writer.WriteString("name", "example");
writer.WriteEndObject();
writer.Flush();

// Reset with different options for next use — no new allocation needed
stream.SetLength(0);
writer.Reset(stream, new JsonWriterOptions { Indented = false });

Miglioramenti di SerializeAsyncEnumerable

JsonSerializer.SerializeAsyncEnumerable ottiene due nuove funzionalità:

  • PipeWriter target: Per gli scenari di I/O basati su pipeline, i nuovi overload accettano un PipeWriter come destinazione di output, semplificando l'integrazione diretta dello streaming JSON.
  • topLevelValues parametro: per l'output NDJSON, impostare questo parametro su true per scrivere ogni elemento come valore JSON di primo livello separato da nuove righe anziché eseguire il wrapping di tutti gli elementi in una matrice JSON. Entrambi i sovraccarichi Stream e PipeWriter supportano il parametro.
static async IAsyncEnumerable<int> GenerateNumbers()
{
    for (int i = 0; i < 5; i++)
    {
        yield return i;
        await Task.Yield();
    }
}

var pipe = new Pipe();

// Write a JSON array: [0,1,2,3,4]
await JsonSerializer.SerializeAsyncEnumerable(
    pipe.Writer,
    GenerateNumbers());

// Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
await JsonSerializer.SerializeAsyncEnumerable(
    pipe.Writer,
    GenerateNumbers(),
    topLevelValues: true);

Serializzazione dei tipi di unione C#

System.Text.Json ora può serializzare e deserializzare i tipi di unione C#. Il serializzatore riconosce un'unione tramite il nuovo JsonTypeInfoKind.Union tipo di contratto, legge e scrive il caso attivo e supporta sia il serializzatore basato sulla riflessione sia il generatore di codice sorgente. Quando si serializza un'unione, System.Text.Json scrive il valore del caso attivo, quindi un'unione di int e string viene serializzata e deserializzata senza problemi:

{
  "id": 1,
  "payload": "hello"
}
{
  "id": 2,
  "payload": 42
}

Le nuove API JsonUnionAttribute e JsonUnionCaseInfo, insieme alle API di classificazione dei tipi (JsonTypeClassifier e JsonSerializerOptions.TypeClassifiers), consentono di personalizzare il modo in cui i casi vengono individuati e denominati. I tipi di unione sono una funzionalità di anteprima del linguaggio C#. Per altre informazioni, vedere Novità di C# 15.

Inferenza del polimorfismo per gerarchia chiusa

JsonSerializerOptions aggiunge JsonSerializerOptions.InferClosedTypePolymorphism in modo che il serializzatore possa dedurre metadati polimorfici per le gerarchie chiuse C# senza richiedere annotazioni esplicite JsonDerivedTypeAttribute su ogni tipo di base. Le registrazioni esplicite hanno ancora la precedenza.

Miglioramenti delle espressioni regolari

Opzione AnyNewLine

Un nuovo RegexOptions.AnyNewLine flag rende ^, $e . considera il set completo di caratteri di nuova riga Unicode come caratteri di terminazione di riga, non solo \n. Ciò consente di analizzare il testo che combina Windows (\r\n), Unix (\n) e le terminazioni di riga specifiche di Unicode (\u0085, \u2028, \u2029).

string text = "line1\r\nline2\u0085line3\u2028line4";

// RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
// sequences as line terminators, not just \n.
MatchCollection matches = Regex.Matches(
    text,
    @"^line\d$",
    RegexOptions.Multiline | RegexOptions.AnyNewLine);

Console.WriteLine(matches.Count); // 4

Correzioni del motore regex e del generatore di origine

.NET 11 include diverse correzioni regex e correzioni di qualità del codice:

  • Il motore senza backtracking non impiega più un tempo superlineare in determinati schemi di cicli annidati e produce risultati corretti nei casi che in precedenza portavano a divergenza.
  • Il compilatore di espressioni regolari e il generatore di codice sorgente gestiscono correttamente resumeAt quando un costrutto condizionale compare all'interno del corpo di un ciclo.
  • L'SYSLIB1045 correzione del codice non crea più nomi di classe duplicati quando vengono applicati in più dichiarazioni parziali della stessa classe.

Formati di compressione e archiviazione

Miglioramenti della compressione

.NET 11 include diversi miglioramenti alle API di compressione.

Modalità di accesso agli elementi dell'archivio ZIP

La ZipArchiveEntry classe supporta ora l'apertura di voci con modalità di accesso ai file specifiche tramite nuovi overload: ZipArchiveEntry.Open(FileAccess) e ZipArchiveEntry.OpenAsync(FileAccess, CancellationToken). Queste sovraccarichi accettano un parametro FileAccess e consentono di aprire file ZIP per l'accesso di lettura, di scrittura o di lettura/scrittura.

Inoltre, una nuova CompressionMethod proprietà espone il metodo di compressione usato per un elemento tramite l'enumerazione ZipCompressionMethod, che include i valori per Stored, Deflate e Deflate64.

Validazione CRC32 di ZIP

ZipArchive verifica il checksum CRC32 durante la lettura degli elementi dell’archivio ZIP. Gli archivi danneggiati o troncati passati in precedenza senza errori ora generano InvalidDataException, consentendo di rilevare tempestivamente i problemi di integrità dei dati.

Supporto per password di archivi ZIP

ZipArchiveEntry ora supporta elementi protetti da password per le operazioni di lettura e scrittura. Usa i nuovi overload con supporto per password ZipArchiveEntry.Open(ReadOnlySpan<Char>) e ZipEncryptionMethod, e usa password e ZipArchiveEntry.OpenAsync(ReadOnlySpan<Char>, CancellationToken) quando crei voci criptate.

Modifica del comportamento DeflateStream e GZipStream

A partire da .NET 11, DeflateStream e GZipStream scrivono sempre le intestazioni e i piè di pagina del formato nel flusso di output, anche quando non viene scritto alcun dato. In questo modo l'output è un flusso compresso valido in base alle specifiche Deflate e GZip.

In precedenza, questi flussi non producevano alcun output se non è stato scritto alcun dato, generando un flusso di output vuoto. Questa modifica garantisce la compatibilità con gli strumenti che prevedono flussi compressi formattati correttamente.

Per ulteriori informazioni, vedere DeflateStream e GZipStream scrivono intestazioni e piè di pagina per payload vuoti.

API Deflate, ZLib e GZip basate su Span

System.IO.Compression ora offre Span<byte>/ReadOnlySpan<byte> punti di ingresso codificati e decodificati per i formati Deflate, ZLib e GZip. Le nuove API, per tipi come DeflateEncoder, ZLibEncoder e GZipEncoder, rispecchiano la struttura di BrotliEncoder/BrotliDecoder e delle primitive Zstandard. È possibile comprimere e decomprimere i buffer senza allocare un oggetto Stream. Questo è utile in scenari ad alta velocità di elaborazione, come parser di protocolli, strumenti di inoltro dei log e middleware che operano già su span.

ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
byte[] buffer = new byte[source.Length + 32];
Span<byte> destination = buffer;

using ZLibEncoder encoder = new();
OperationStatus status = encoder.Compress(
    source, destination, out int bytesConsumed, out int bytesWritten,
    isFinalBlock: true);

Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");

Compressione Zstandard

Le API di compressione Zstandard, ad esempio ZstandardStream e ZstandardEncoder, ora fanno parte del namespace System.IO.Compression, insieme a DeflateStream, GZipStream e BrotliStream. La superficie dell'API è in caso contrario invariata.

Selezione del formato archivio Tar

Nuovi sovraccarichi su CreateFromDirectory e CreateFromDirectoryAsync accettano un parametro TarEntryFormat, offrendo il controllo diretto sul formato di archivio. In precedenza, CreateFromDirectory produceva sempre archivi Pax. I nuovi overload supportano tutti e quattro i formati tar( Pax, Ustar, GNU e V7) per la compatibilità con strumenti e ambienti specifici.

// Create a GNU format tar archive for Linux compatibility
TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
    includeBaseDirectory: true, format: TarEntryFormat.Gnu);

// Create a Ustar format archive for broader compatibility
using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
TarFile.CreateFromDirectory("/source/dir", outputStream,
    includeBaseDirectory: false, format: TarEntryFormat.Ustar);

// Async version
CancellationToken cancellationToken = CancellationToken.None;
await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
    includeBaseDirectory: true, format: TarEntryFormat.Pax,
    cancellationToken: cancellationToken);

TarReader può ora anche leggere le voci che utilizzano la rappresentazione del formato GNU sparse 1.0 (PAX). La rappresentazione 0.1 precedente era già supportata. Con il supporto 1.0 abilitato, TarReader corrisponde a ciò che le moderne implementazioni di tar scrivono per impostazione predefinita per i file sparsi.

Raccolte, numeri e I/O di basso livello

Miglioramenti all'aggiunta a LINQ

LINQ aggiunge miglioramenti ai join, tra cui una nuova operazione FullJoin e overload che restituiscono tuple per i metodi Join e GroupJoin esistenti. Queste API sono disponibili in Enumerable, Queryablee AsyncEnumerable.

LeftJoin, RightJoin e FullJoin

Poiché le tre operazioni di join esterno restituiscono direttamente tuple, è possibile utilizzarle facilmente tramite la corrispondenza di pattern e la decostruzione:

  • Enumerable.LeftJoin — Restituisce tutti gli elementi dalla sequenza sinistra (esterna). Quando non è presente alcun elemento corrispondente nella sequenza di destra (interna), l'elemento interno è default (in genere null per i tipi di riferimento).
  • Enumerable.RightJoin — Restituisce tutti gli elementi dalla sequenza destra (interna). Quando non è presente alcun elemento corrispondente nella sequenza di sinistra (esterna), l'elemento della sequenza esterna è default (in genere null per i tipi di riferimento).
  • Enumerable.FullJoin — Restituisce tutti gli elementi di entrambe le sequenze, utilizzando default su entrambi i lati quando non esiste alcuna corrispondenza.
var products = new List<(int Id, string Name, string? Category)>
{
    (1, "Laptop", "Electronics"),
    (2, "Mouse", "Electronics"),
    (3, "Orphan", null), // No matching category
};
var categories = new List<(string Name, string Description)>
{
    ("Electronics", "Electronic devices"),
    ("Furniture", "Office furniture"), // No matching product
};

// LeftJoin: all products, matched categories (null if none)
var leftJoined = products.LeftJoin(
    categories,
    p => p.Category,
    c => c.Name);

foreach (var (product, category) in leftJoined)
    Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
// Laptop: Electronic devices
// Mouse: Electronic devices
// Orphan: (none)

// FullJoin: all products and categories, paired where they match
var fullJoined = products.FullJoin(
    categories,
    p => p.Category,
    c => c.Name);

foreach (var (product, category) in fullJoined)
    Console.WriteLine(
        $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
// Laptop: Electronic devices
// Mouse: Electronic devices
// Orphan: (none)
// (none): Office furniture

Tipi in virgola mobile decimale IEEE 754

System.Numerics aggiunge Decimal32, Decimal64 e Decimal128, che implementano la semantica in virgola mobile decimale IEEE 754-2019. Questi tipi supportano valori matematici, infiniti e NaN generici e possono essere utili quando è necessario un comportamento decimale IEEE anziché Decimal.

Analisi numerica parziale

INumberBase<TSelf>.TryParsePartial aggiunge overload di analisi parziali che segnalano l'input utilizzato. Usare questa API per analizzare i formati delimitati, ad esempio CSV, in modo da non copiare sottostringhe.

Generico complesso<T>

Complex<T>è ora disponibile, quindi un'aritmetica complessa può usare diversi tipi a virgola mobile, tra cui float, HalfBFloat16, e i nuovi tipi a virgola mobile decimali.

Overload di Join e GroupJoin che restituiscono tuple

I nuovi sovraccarichi di Enumerable.Join e Enumerable.GroupJoin restituiscono tuple direttamente senza richiedere un selettore di risultato e accettano un parametro facoltativo IEqualityComparer<T>. L'overload facoltativo del comparatore è disponibile anche per le operazioni FullJoin, LeftJoin e RightJoin.

var products = new[] { (Id: 1, Name: "Laptop"), (Id: 2, Name: "Mouse") };
var orders = new[] { (ProductId: 1, Qty: 3), (ProductId: 1, Qty: 1) };

var result = products.Join(orders, p => p.Id, o => o.ProductId);
foreach (var (product, order) in result)
    Console.WriteLine($"{product.Name}: qty={order.Qty}");
// Laptop: qty=3
// Laptop: qty=1

Supporto di BFloat16 in BitConverter

La BitConverter classe include ora metodi per la conversione tra BFloat16 valori e matrici di byte o rappresentazioni di bit. Questi nuovi metodi includono:

BFloat16 (Brain Floating Point) è un formato a virgola mobile a 16 bit comunemente usato nell'apprendimento automatico e nel calcolo scientifico.

Formattazione e analisi esadecimale a virgola mobile

double, floate Half ora possono essere formattati e analizzati nel formato IEEE-754 esadecimale. La forma esadecimale preserva ogni bit del valore sottostante, il che la rende la scelta giusta per i test con golden file, l'interoperabilità tra linguaggi con C/C++ printf("%a", ...) e qualsiasi scenario in cui il passaggio di andata e ritorno di un double tramite testo decimale comporta una perdita eccessiva.

double value = Math.PI;

// Format as hexadecimal IEEE-754: preserves all bits exactly
string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

Console.WriteLine(roundTripped == value); // True — exact round-trip

Miglioramenti numerici

Matrix4x4.GetDeterminant() ora usa un'implementazione con vettorizzazione SSE, migliorando le prestazioni di circa 15%.

Metodi generici casuali

Random ottiene due nuovi metodi generici che funzionano con qualsiasi tipo numerico che implementa le interfacce matematiche generiche appropriate:

  • Random.NextInteger — Genera un numero intero casuale di tipo T in cui T implementa IBinaryInteger<T> e IMinMaxValue<T>. Gli overload accettano un limite superiore oppure sia un limite inferiore sia un limite superiore.
  • Random.NextBinaryFloat — Genera un valore a virgola mobile casuale di tipo T in cui T implementa IBinaryFloatingPointIeee754<T>.
// Generate a random integer of any binary integer type
int i = Random.Shared.NextInteger<int>();
long l = Random.Shared.NextInteger<long>(0L, 100L);
byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

// Generate a random floating-point value of any IEEE-754 type
float f = Random.Shared.NextBinaryFloat<float>();
double d = Random.Shared.NextBinaryFloat<double>();
Half h = Random.Shared.NextBinaryFloat<Half>();

Console.WriteLine($"int={i}, long={l}, byte={b}");
Console.WriteLine($"float={f}, double={d}, Half={h}");

Operazioni vettoriali tra lane

Vector64<T>, Vector128<T>, Vector256<T>Vector512<T>, e Vector<T> ottengono un set di metodi di costruzione e composizione di corsie che in precedenza richiedevano shuffle scritti a mano. I nuovi metodi rientrano in alcune famiglie:

  • Costruzione con pattern:CreateGeometricSequence, CreateAlternatingSequencee CreateHarmonicSequence compila un vettore da un valore iniziale e una regola.
  • Interleave e de-interleave:Zip, ZipLower/ZipUpper, Unzip, UnzipEven/UnzipOdd.
  • Riorganizzare: Famiglia Concat (ConcatLowerLower, ConcatLowerUpper, ConcatUpperLower, ConcatUpperUpper) e Reverse.
using System.Runtime.Intrinsics;

// {1, 2, 4, 8} — each lane is the previous lane times two
Vector128<int> powers = Vector128.CreateGeometricSequence(1, 2);

// Interleave two vectors lane-by-lane
(Vector128<int> lower, Vector128<int> upper) =
    Vector128.Zip(Vector128.Create(1), Vector128.Create(2));

Questi blocchi predefiniti sono utili per l'elaborazione di immagini, l'elaborazione dei segnali audio digitali (DSP) e altri carichi di lavoro a elevato utilizzo di SIMD che richiedono un controllo granulare sul layout degli elementi vettoriali, senza tornare a intrinseci specifici della piattaforma.

Miglioramenti di I/O di basso livello

Supporto della pipe SafeFileHandle

SafeFileHandle ottiene due nuovi membri:

SafeFileHandle.CreateAnonymousPipe(
    out SafeFileHandle readEnd,
    out SafeFileHandle writeEnd,
    asyncRead: true,
    asyncWrite: false);

using (readEnd)
using (writeEnd)
{
    // SafeFileHandle.Type reports the kind of OS object the handle refers to
    Console.WriteLine(readEnd.Type);   // Pipe
    Console.WriteLine(writeEnd.Type);  // Pipe
}

Supporto della pipe di accesso casuale

RandomAccess.Read e RandomAccess.Write ora funzionano con handle non ricercabili, ad esempio pipe, oltre ai normali handle di file.

Su Windows, Process ora utilizza operazioni di I/O sovrapposte per stdout/stderr reindirizzati, riducendo il blocco del pool di thread nelle applicazioni ad alto utilizzo di processi.

Miglioramenti delle raccolte

BitArray.PopCount

La BitArray classe include ora un BitArray.PopCount() metodo che restituisce il numero di bit impostati su true nella matrice. In questo modo è possibile contare i bit impostati in modo efficiente senza scorrere manualmente la matrice.

Supporto di IReadOnlySet nella serializzazione JSON

La JsonMetadataServices classe include ora un JsonMetadataServices.CreateIReadOnlySetInfo metodo, abilitando il supporto della serializzazione JSON per IReadOnlySet<T> le raccolte.

EqualityComparer<T>.Create

Per creare un comparatore di uguaglianza da una funzione di selezione della chiave, usare il nuovo metodo factory EqualityComparer<T>.Create. È possibile passare un valore facoltativo IEqualityComparer<T> per il tipo di chiave stesso:


// Create an equality comparer based on a key selector
var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

var people = new HashSet<(string Name, int Age)>(byName)
{
    ("Alice", 30),
    ("Bob", 25),
    ("Alice", 40), // Duplicate by name — not added
};
Console.WriteLine(people.Count); // 2

Estensioni e piattaforma per sviluppatori

Generazione di codice per unioni con discriminante

Note

Questa è una funzionalità di anteprima in .NET 11.

.NET 11 introduce System.Runtime.CompilerServices.UnionAttribute e System.Runtime.CompilerServices.IUnion in System.Runtime.CompilerServices. Questi tipi sono il lato runtime della progettazione di unioni discriminate C#. Non sono ancora esposti direttamente agli utenti — il compilatore C# e i generatori di codice sorgente dovrebbero essere i principali elementi che li producono — ma sono inclusi nel framework affinché le librerie possano sviluppare già da ora rispetto a questa superficie API.

Per la progettazione sul lato linguaggio, vedere la proposta di unioni C#.

Novità di MetadataLoadContext

MetadataLoadContext.GetLoadContext(Assembly) restituisce il contesto di caricamento che ha prodotto un determinato Assembly, rispecchiando l'API esistente da tempo in AssemblyLoadContext. In questo modo si chiude un divario per gli strumenti che riflettono gli assembly in un isolato MetadataLoadContext e deve tornare da un Assembly riferimento al contesto proprietario:

using System.Reflection;
using System.Reflection.Metadata;

string[] paths = [typeof(object).Assembly.Location];
using var mlc = new MetadataLoadContext(new PathAssemblyResolver(paths));
Assembly asm = mlc.LoadFromAssemblyPath(typeof(object).Assembly.Location);

MetadataLoadContext owner = MetadataLoadContext.GetLoadContext(asm)!;
Console.WriteLine(ReferenceEquals(owner, mlc)); // true

Costante dello schema dati URI

È stata aggiunta una nuova Uri.UriSchemeData costante che rappresenta lo data: schema URI. Questa costante fornisce un modo standardizzato per fare riferimento agli URI dei dati.

Miglioramenti alla riflessione

Type.GetNullableUnderlyingType

System.Type ottiene un nuovo metodo virtuale, Type.GetNullableUnderlyingType(), che restituisce il tipo di valore sottostante per un tipo Nullable<T>, o null per qualsiasi tipo che non ammette valori null. Le implementazioni vengono fornite in Type, TypeBuilderEnumBuilder, GenericTypeParameterBuilder, e TypeDelegator:

Type nullableIntType = typeof(int?);
Type? underlying = nullableIntType.GetNullableUnderlyingType();
Console.WriteLine(underlying); // System.Int32

Type nonNullable = typeof(int);
Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True

ConstructorInfo.GetGenericArguments

ConstructorInfo.GetGenericArguments() ora ha un override, fornendo un modo coerente per recuperare argomenti di tipo generico per le definizioni del costruttore, che corrispondono al comportamento già disponibile in altre MethodBase sottoclassi.

Override di Assembly.Location per AssemblyLoadContext

AssemblyLoadContext.SetAssemblyLocationOverride(Func<Assembly,String,String>) è un callback impostabile una sola volta che può sovrascrivere la posizione riportata da Assembly.Location. Questa API consente agli host che organizzano assembly in percorsi temporanei o posizioni virtualizzate di segnalare posizioni stabili e significative per la diagnostica e il codice di caricamento delle risorse.

Riduzione della contesa in System.IO.Pipelines

System.IO.Pipelines riduce la contenzione dei lock di lettura e scrittura nei carichi di lavoro con elevato fan-in. Le modifiche interne includono un ambito di blocco inferiore, la pianificazione della continuazione della coda locale e gli aggiornamenti del pool che migliorano la località della cache.

Miglioramenti dell'attributo StringSyntax

La StringSyntaxAttribute classe include ora costanti per i linguaggi di programmazione comuni:

Queste costanti possono essere usate con l'attributo StringSyntax per fornire un migliore supporto degli strumenti per i valori letterali stringa contenenti codice in questi linguaggi.

Memorizzazione nella cache, configurazione e registrazione

Legame di configurazione

Microsoft.Extensions.Configuration aggiunge Microsoft.Extensions.Configuration.ConfigurationIgnoreAttribute, così i modelli possono escludere singole proprietà dal binding in modo dichiarativo, senza basarsi su opzioni BindNonPublicProperties o convertitori personalizzati:

public sealed class AppOptions
{
    public string Endpoint { get; set; } = "";

    [ConfigurationIgnore]
    public string ComputedKey => Endpoint + ":default";
}

ConfigurationBinder associa ora anche una matrice vuota a un parametro del costruttore anziché generare un'eccezione.

PhysicalFilesWatcher non genera più quando la directory radice non esiste ancora e InMemoryDirectoryInfo risolve .. e altri segmenti relativi in modo coerente con il provider fisico.

Metriche di MemoryCache OpenTelemetry

MemoryCache ora genera un set predefinito di metriche compatibili con OpenTelemetry (OTel) senza un pacchetto di adattatori aggiuntivo. Per attivare questa opzione, impostare MemoryCacheOptions.TrackStatistics su true:

var cache = new MemoryCache(new MemoryCacheOptions
{
    TrackStatistics = true
});

Il nuovo contatore Microsoft.Extensions.Caching.Memory.MemoryCache pubblica quattro strumenti osservabili:

  • dotnet.cache.requests (con un dotnet.cache.request.type tag che distingue hit da miss)
  • dotnet.cache.evictions
  • dotnet.cache.entries
  • dotnet.cache.estimated_size

Passa un System.Diagnostics.Metrics.IMeterFactory al nuovo sovraccarico del costruttore MemoryCache.MemoryCache(IOptions<MemoryCacheOptions>, ILoggerFactory, IMeterFactory) per metriche specifiche dell'istanza. Senza uno, gli strumenti sono aggregati a livello di processo su un contatore condiviso.

Miglioramenti alla convalida del generatore di opzioni

OptionsBuilder<TOptions> ottiene un nuovo overload generico OptionsBuilder<TOptions>.Validate che accetta un parametro di tipo anziché un delegato factory. Il tipo deve implementare IValidateOptions<TOptions> ed essere registrato nel contenitore di dependency injection. Ciò allinea la validazione delle opzioni al pattern DI standard:

services.AddSingleton<IValidateOptions<MyOptions>, MyOptionsValidator>();
services.AddOptions<MyOptions>()
    .Bind(configuration.GetSection("MyOptions"))
    .Validate<MyOptionsValidator>();

ChangeToken.OnChange asincrono

OnChange ora dispone di Func<Task> sovraccarichi, quindi puoi eseguire callback asincrone senza usare async void. La nuova registrazione avviene ora dopo il completamento dell'attività restituita.

Factory HybridCache con riconoscimento delle opzioni

GetOrCreateAsync ora dispone di overload che forniscono un contesto factory modificabile. È possibile impostare le opzioni per la singola voce, ad esempio la scadenza e la dimensione della cache locale, dal callback che produce valori.

Convalida asincrona con DataAnnotations

System.ComponentModel.DataAnnotations supporta ora la convalida asincrona. Una regola di convalida che deve eseguire operazioni di I/O, ovvero una ricerca di database o una chiamata API remota, può ora essere eseguita senza bloccare un thread. Esistono tre nuovi modi per esprimere una regola asincrona:

  • Derivare da AsyncValidationAttribute ed eseguire l'override del relativo IsValidAsync metodo.
  • Implementare IAsyncValidatableObject nel modello.
  • Chiamare i nuovi Validator.ValidateObjectAsyncmetodi , TryValidateObjectAsyncValidatePropertyAsync, e ValidateValueAsync .
using System;
using System.ComponentModel.DataAnnotations;

public sealed class ValidVatNumberAttribute : AsyncValidationAttribute
{
    protected override ValidationResult? IsValid(object? value, ValidationContext context) =>
        throw new InvalidOperationException("Validate this attribute with IsValidAsync.");

    protected override async Task<ValidationResult?> IsValidAsync(
        object? value, ValidationContext context, CancellationToken cancellationToken)
    {
        var registry = context.GetRequiredService<IVatRegistry>();
        return await registry.IsRegisteredAsync((string?)value, cancellationToken)
            ? ValidationResult.Success
            : new ValidationResult("That VAT number isn't registered with the tax authority.");
    }
}

public class Invoice
{
    [Required]
    [StringLength(14, MinimumLength = 8)]
    [RegularExpression(@"^[A-Z]{2}[A-Z0-9]+$")]
    [ValidVatNumber] // asynchronous
    public string VatNumber { get; set; } = "";
}

var context = new ValidationContext(model, serviceProvider, items: null);
await Validator.ValidateObjectAsync(model, context, validateAllProperties: true);

Microsoft.Extensions.Options aggiunge il supporto per la corrispondenza: le opzioni possono essere validate in modo asincrono, anche all'avvio tramite il nuovo IAsyncStartupValidator. In questo modo, un'app fallisce subito se un'opzione che richiede un controllo di rete è configurata in modo errato.

Configurazione del tracciamento attività

Una nuova API di configurazione del tracciamento in Microsoft.Extensions.Diagnostics consente di attivare e disattivare il tracciamento Activity con regole anziché configurare manualmente istanze di ActivityListener. Chiamare AddTracing e descrivere le origini attività, le operazioni e i listener da abilitare o disabilitare. Le regole possono essere definite tramite configurazione, in modo che il tracciamento possa essere regolato senza effettuare una nuova distribuzione.

builder.Services.AddTracing(tracing =>
{
    tracing.EnableTracing(sourceName: "MyCompany.Orders");
    tracing.DisableTracing(sourceName: "MyCompany.Orders", operationName: "HealthCheck");
});

La stessa release aggiunge ActivitySourceFactory e rende disponibile ActivitySource, che insieme consentono la creazione tramite factory e listener aggiornabili.

Metodi generici di registrazione generati dal codice sorgente

Il generatore di codice sorgente per la registrazione ora supporta metodi generici contrassegnati con LoggerMessageAttribute, principalmente per evitare il boxing. I parametri di tipo generico possono usare vincoli standard, ad esempio class, struct, unmanaged, interfacce, tipi di base e new(). L'allows ref struct anti-vincolo non è supportato e produce SYSLIB1011.

[LoggerMessage(0, LogLevel.Trace, "Received {Code} ({Length} bytes)")]
public static partial void PacketReceived<TCode>(ILogger logger, TCode code, int length)
    where TCode : struct, Enum;

Maggiore affidabilità per il rilevamento delle modifiche ai file di configurazione

Il PhysicalFilesWatcher tipo, usato dai provider di configurazione per rilevare le modifiche apportate ai file di configurazione, include diversi miglioramenti dell'affidabilità.

Ora gestisce in modo più affidabile i percorsi relativi, le directory mancanti e i file system che non supportano il monitoraggio dei file. Evita inoltre di controllare le sottodirectory quando non è necessario.

Cryptography

Scambio di chiavi X25519 Diffie-Hellman

La nuova System.Security.Cryptography.X25519DiffieHellman classe astratta fornisce un'API pulita per l'algoritmo di Diffie-Hellman curva ellittica X25519, ampiamente usato nelle implementazioni TLS 1.3, SSH e Signal-protocol. Le implementazioni specifiche della piattaforma vengono fornite tramite X25519DiffieHellmanCng (Windows) e X25519DiffieHellmanOpenSsl (Linux e macOS), ma il metodo factory portabile GenerateKey() seleziona automaticamente quello corretto:

// Generate key pairs for Alice and Bob
using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

// Each party derives the shared secret using the other's public key
byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

// Both parties arrive at the same secret
Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True

La classe supporta il ciclo di vita completo della chiave: generazione di chiavi, PKCS#8 e SubjectPublicKeyInfo import/export, serializzazione PEM e accesso a chiavi private/pubbliche non elaborate.

Overload di CryptographicOperations.FixedTimeEquals

Per gli scenari di confronto di segreti in cui il valore previsto è un singolo byte noto, utilizzare il nuovo sovraccarico CryptographicOperations.FixedTimeEquals che confronta ReadOnlySpan<T> con un singolo valore byte a tempo costante:

bool equal = CryptographicOperations.FixedTimeEquals(receivedSpan, 0x42);

Sicurezza delle reti e dei trasporti

Protezione avanzata dell'handshake TLS

Due System.Net.Security elementi migliorano l'affidabilità di TLS (Transport Layer Security):

  • SslStream correzioni al controllo dei limiti nell'handshake lato server in TlsFrameHelper risolvono diversi casi limite che potrebbero manifestarsi come IOException nei record ClientHello malformati.
  • Su Linux, gli errori di convalida dei certificati vengono ora segnalati al peer come allerte TLS standard, in linea con il comportamento di Windows. I client che si connettono ricevono un messaggio di errore di handshake che consente di intervenire, invece di un'interruzione della connessione.

Compressione delle richieste HTTP

System.Net.Http aggiunge i wrapper GZipCompressedContent, BrotliCompressedContent e ZstandardCompressedContent per i corpi delle richieste. Questi wrapper impostano l'intestazione Content-Encoding e trasmettono in streaming contenuto compresso durante la serializzazione della richiesta.

Eliminazione configurabile delle connessioni HTTP

SocketsHttpHandler ora fornisce SocketsHttpHandler.ShouldEvictConnection per le decisioni di espulsione per ogni connessione. Questo callback consente di ritirare le connessioni in base all'età, ai dettagli dell'endpoint e alle modifiche DNS anziché ai criteri fissi di sola durata.

API di risoluzione dei record DNS

System.Net.Dns aggiunge API tipizzate per la risoluzione dei record, tra cui Dns.ResolveSrv(String), Dns.ResolveMx(String), Dns.ResolveTxt(String), Dns.ResolveCName(String), Dns.ResolvePtr(String) e Dns.ResolveNs(String), insieme alle varianti Async. I risultati includono record, codice di risposta e metadati TTL (Negative-Cache Time to Live) tramite DnsResult<T>.

Declassamento automatico di HTTP/2 per l'autenticazione di Windows

HttpClient effettua automaticamente il downgrade a HTTP/1.1 quando una richiesta richiede autenticazione di Windows (NTLM/Negotiate) su HTTP/2. La specifica HTTP/2 non consente gli schemi di autenticazione associati alla connessione su cui si basa NTLM e Kerberos, quindi queste richieste in precedenza non sono riuscite. Con il downgrade applicato, le applicazioni destinate agli ambienti con autenticazione mista, comuni nelle intranet aziendali, funzionano senza override espliciti HttpRequestMessage.Version.

Priorità del flusso QUIC

QuicStream ottiene una QuicStream.Priority proprietà e una QuicStream.DefaultPriority costante che espone la priorità del flusso HTTP/3 (RFC 9218). I valori di priorità sono compresi tra 0 (massimo) e 255 (più basso), con un valore predefinito pari a 127:

// Increase priority for interactive streams
stream.Priority = 64; // Higher priority than the default 127

Costanti del tipo MIME video

Una nuova System.Net.Mime.MediaTypeNames.Video classe fornisce costanti stringa per i tipi di supporti video comuni:

  • MediaTypeNames.Video.Mp4"video/mp4"
  • MediaTypeNames.Video.Mpeg"video/mpeg"
  • MediaTypeNames.Video.Ogg"video/ogg"
  • MediaTypeNames.Video.QuickTime"video/quicktime"
  • MediaTypeNames.Video.WebM"video/webm"

Queste classi vengono unite alle classi , MediaTypeNames.ApplicationMediaTypeNames.Image, e MediaTypeNames.Text esistentiMediaTypeNames.Multipart.

Accessibilità dell'API non sicura

.NET 11 rimuove l'attributo [RequiresUnsafe] da un ampio set di API che accettano parametri puntatore. In precedenza, la chiamata di questi metodi dal codice che usava unsafe blocchi richiedeva ancora un'impostazione a livello <AllowUnsafeBlocks>true</AllowUnsafeBlocks> di progetto perché l'attributo imponeva tale requisito in modo indipendente. A questo punto, è necessario solo il modificatore di blocco o metodo standard unsafe .

Le API interessate includono:

Questa modifica riduce le difficoltà in scenari avanzati, ad esempio la scrittura di codice di interoperabilità, i file mappati in memoria e la manipolazione di buffer di basso livello, in cui il contesto unsafe è già richiesto dal codice chiamante.

Vedere anche