Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questo articolo descrive le nuove funzionalità nelle librerie .NET per .NET 10.
Crittografia
- Trovare i certificati in base alle identificazioni personali diverse da SHA-1
- Trovare dati codificati in PEM in ASCII/UTF-8
- Algoritmo di crittografia per l'esportazione PKCS#12/PFX
- Crittografia post-quantistica (PQC)
Trova i certificati mediante impronte digitali diverse da SHA-1
La ricerca di certificati in modo univoco tramite identificazione personale è un'operazione piuttosto comune, ma il X509Certificate2Collection.Find(X509FindType, Object, Boolean) metodo (per la FindByThumbprint modalità) cerca solo il valore di identificazione personale SHA-1.
C'è un rischio nell'usare il metodo Find per trovare le impronte SHA-2-256 ("SHA256") e SHA-3-256 poiché entrambi questi algoritmi di hash hanno le stesse lunghezze.
.NET 10 introduce invece un nuovo metodo che accetta il nome dell'algoritmo hash da usare per la corrispondenza.
X509Certificate2Collection coll = store.Certificates.FindByThumbprint(HashAlgorithmName.SHA256, thumbprint);
Debug.Assert(coll.Count < 2, "Collection has too many matches, has SHA-2 been broken?");
return coll.SingleOrDefault();
Trova dati con codifica PEM in ASCII/UTF-8
La codifica PEM (originariamente Privacy Enhanced Mail, ma ora usata ampiamente al di fuori del messaggio di posta elettronica) è definita per "text", il che significa che la classe PemEncoding è stata progettata per l'esecuzione su String e ReadOnlySpan<char>. Tuttavia, è comune ,in particolare in Linux, avere qualcosa di simile a un certificato scritto in un file che usa la codifica ASCII (stringa). Storicamente, ciò significava che era necessario aprire il file e convertire i byte in caratteri (o una stringa) prima di poter usare PemEncoding.
Il nuovo PemEncoding.FindUtf8(ReadOnlySpan<Byte>) metodo sfrutta il fatto che PEM è definito solo per i caratteri ASCII a 7 bit e che ASCII a 7 bit ha una sovrapposizione perfetta con valori UTF-8 a byte singolo. Chiamando questo nuovo metodo, è possibile ignorare la conversione da UTF-8/ASCII a char e leggere direttamente il file.
byte[] fileContents = File.ReadAllBytes(path);
-char[] text = Encoding.ASCII.GetString(fileContents);
-PemFields pemFields = PemEncoding.Find(text);
+PemFields pemFields = PemEncoding.FindUtf8(fileContents);
-byte[] contents = Base64.DecodeFromChars(text.AsSpan()[pemFields.Base64Data]);
+byte[] contents = Base64.DecodeFromUtf8(fileContents.AsSpan()[pemFields.Base64Data]);
Algoritmo di crittografia per l'esportazione PKCS#12/PFX
I nuovi ExportPkcs12 metodi su X509Certificate consentono ai chiamanti di scegliere quali algoritmi di crittografia e digest vengono usati per produrre l'output.
- Pkcs12ExportPbeParameters.Pkcs12TripleDesSha1 indica lo standard di fatto dell'era windows XP. Produce un output supportato da quasi ogni libreria e piattaforma che supporta la lettura di PKCS#12/PFX scegliendo un algoritmo di crittografia meno recente.
- Pkcs12ExportPbeParameters.Pbes2Aes256Sha256 indica che AES deve essere usato invece di 3DES (e SHA-2-256 anziché SHA-1), ma l'output potrebbe non essere compreso da tutti i lettori (ad esempio Windows XP).
Se si desidera un maggiore controllo, è possibile usare l'overload che accetta un oggetto PbeParameters.
Crittografia post-quantistica (PQC)
.NET 10 include il supporto per tre nuovi algoritmi asimmetrici: ML-KEM (FIPS 203), ML-DSA (FIPS 204) e SLH-DSA (FIPS 205). I nuovi tipi sono:
- System.Security.Cryptography.MLKem
- System.Security.Cryptography.MLDsa
- System.Security.Cryptography.SlhDsa
Poiché aggiunge poco vantaggio, questi nuovi tipi non derivano da AsymmetricAlgorithm. Anziché l'approccio AsymmetricAlgorithm alla creazione di un oggetto e quindi all'importazione di una chiave in esso o alla generazione di una chiave aggiornata, i nuovi tipi usano tutti i metodi statici per generare o importare una chiave:
using System;
using System.IO;
using System.Security.Cryptography;
private static bool ValidateMLDsaSignature(ReadOnlySpan<byte> data, ReadOnlySpan<byte> signature, string publicKeyPath)
{
string publicKeyPem = File.ReadAllText(publicKeyPath);
using (MLDsa key = MLDsa.ImportFromPem(publicKeyPem))
{
return key.VerifyData(data, signature);
}
}
E invece di impostare le proprietà degli oggetti e avere una chiave materializzarsi, la generazione di chiavi in questi nuovi tipi accetta tutte le opzioni necessarie.
using (MLKem key = MLKem.GenerateKey(MLKemAlgorithm.MLKem768))
{
string publicKeyPem = key.ExportSubjectPublicKeyInfoPem();
...
}
Tutti questi algoritmi continuano con il modello di avere una proprietà statica IsSupported per indicare se l'algoritmo è supportato nel sistema corrente.
.NET 10 include l'API di crittografia Windows: supporto CNG (Next Generation) per la crittografia post-quantistica (PQC), che rende questi algoritmi disponibili nei sistemi Windows con supporto PQC. Per esempio:
using System;
using System.IO;
using System.Security.Cryptography;
private static bool ValidateMLDsaSignature(ReadOnlySpan<byte> data, ReadOnlySpan<byte> signature, string publicKeyPath)
{
string publicKeyPem = File.ReadAllText(publicKeyPath);
using MLDsa key = MLDsa.ImportFromPem(publicKeyPem);
return key.VerifyData(data, signature);
}
Gli algoritmi PQC sono disponibili nei sistemi in cui le librerie di crittografia di sistema sono OpenSSL 3.5 (o versioni successive) o Windows CNG con supporto PQC. Il MLKem tipo non è contrassegnato come [Experimental], ma alcuni dei relativi metodi sono (e saranno fino a quando non vengono finalizzati gli standard sottostanti). Le classi MLDsa, SlhDsa e CompositeMLDsa vengono contrassegnate come [Experimental] sotto diagnostica SYSLIB5006 fino al completamento dello sviluppo.
ML-DSA
La MLDsa classe include funzionalità di facilità d'uso che semplificano i modelli di codice comuni:
private static byte[] SignData(string privateKeyPath, ReadOnlySpan<byte> data)
{
using (MLDsa signingKey = MLDsa.ImportFromPem(File.ReadAllBytes(privateKeyPath)))
{
- byte[] signature = new byte[signingKey.Algorithm.SignatureSizeInBytes];
- signingKey.SignData(data, signature);
+ return signingKey.SignData(data);
- return signature;
}
}
.NET 10 aggiunge inoltre il supporto per HashML-DSA, denominato "PreHash" per distinguerlo da ML-DSA "puro". Quando la specifica sottostante interagisce con il valore OID (Object Identifier), i metodi SignPreHash e VerifyPreHash su questo [Experimental] tipo accettano l'OID decimale punteggiato come stringa. Questo può evolversi man mano che più scenari che usano HashML-DSA diventano ben definiti.
private static byte[] SignPreHashSha3_256(MLDsa signingKey, ReadOnlySpan<byte> data)
{
const string Sha3_256Oid = "2.16.840.1.101.3.4.2.8";
return signingKey.SignPreHash(SHA3_256.HashData(data), Sha3_256Oid);
}
A partire da RC 1, ML-DSA supporta anche le firme create e verificate da un valore mu "esterno", che offre maggiore flessibilità per scenari di crittografia avanzati:
private static byte[] SignWithExternalMu(MLDsa signingKey, ReadOnlySpan<byte> externalMu)
{
return signingKey.SignMu(externalMu);
}
private static bool VerifyWithExternalMu(MLDsa verifyingKey, ReadOnlySpan<byte> externalMu, ReadOnlySpan<byte> signature)
{
return verifyingKey.VerifyMu(externalMu, signature);
}
ML-DSA compositi
.NET 10 introduce nuovi tipi per supportare ietf-lamps-pq-composite-sigs (al draft 8 di .NET 10 GA), inclusi i CompositeMLDsa e CompositeMLDsaAlgorithm, con l'implementazione dei metodi primitivi per le varianti RSA.
var algorithm = CompositeMLDsaAlgorithm.MLDsa65WithRSA4096Pss;
using var privateKey = CompositeMLDsa.GenerateKey(algorithm);
byte[] data = [42];
byte[] signature = privateKey.SignData(data);
using var publicKey = CompositeMLDsa.ImportCompositeMLDsaPublicKey(algorithm, privateKey.ExportCompositeMLDsaPublicKey());
Console.WriteLine(publicKey.VerifyData(data, signature)); // True
signature[0] ^= 1; // Tamper with signature
Console.WriteLine(publicKey.VerifyData(data, signature)); // False
AES KeyWrap with Padding (IETF RFC 5649)
AES-KWP è un algoritmo usato occasionalmente in costruzioni come Cryptographic Message Syntax (CMS) EnvelopedData, dove il contenuto viene crittografato una sola volta, ma la chiave di decrittografia deve essere distribuita a più parti, ognuna in un formato segreto distinto.
.NET supporta ora l'algoritmo AES-KWP tramite metodi di istanza nella Aes classe :
private static byte[] DecryptContent(ReadOnlySpan<byte> kek, ReadOnlySpan<byte> encryptedKey, ReadOnlySpan<byte> ciphertext)
{
using (Aes aes = Aes.Create())
{
aes.SetKey(kek);
Span<byte> dek = stackalloc byte[256 / 8];
int length = aes.DecryptKeyWrapPadded(encryptedKey, dek);
aes.SetKey(dek.Slice(0, length));
return aes.DecryptCbc(ciphertext);
}
}
Globalizzazione e data/ora
- Nuovi overload dei metodi in ISOWeek per il tipo DateOnly
- Ordinamento numerico per il confronto tra stringhe
-
Nuovo
TimeSpan.FromMillisecondssovraccarico con unico parametro
Nuovi overload del metodo in ISOWeek per il tipo DateOnly
La classe ISOWeek è stata originariamente progettata per funzionare esclusivamente con DateTime, come è stato introdotto prima dell'esistenza del tipo DateOnly. Ora che DateOnly è disponibile, ha senso anche ISOWeek supportarlo. I seguenti sovraccarichi sono nuovi:
Ordinamento numerico per il confronto tra stringhe
Il confronto di stringhe numeriche è una funzionalità molto richiesta per confrontare le stringhe numericamente anziché lessicograficamente. Ad esempio, 2 è minore di 10, quindi "2" dovrebbe essere visualizzato prima di "10" quando ordinato numericamente. Analogamente, "2" e "02" sono uguali numericamente. Con la nuova NumericOrdering opzione, è ora possibile eseguire questi tipi di confronti:
StringComparer numericStringComparer = StringComparer.Create(CultureInfo.CurrentCulture, CompareOptions.NumericOrdering);
Console.WriteLine(numericStringComparer.Equals("02", "2"));
// Output: True
foreach (string os in new[] { "Windows 8", "Windows 10", "Windows 11" }.Order(numericStringComparer))
{
Console.WriteLine(os);
}
// Output:
// Windows 8
// Windows 10
// Windows 11
HashSet<string> set = new HashSet<string>(numericStringComparer) { "007" };
Console.WriteLine(set.Contains("7"));
// Output: True
Questa opzione non è valida per le operazioni stringa basate su indice seguenti: IndexOf, LastIndexOf, StartsWith, EndsWith, IsPrefixe IsSuffix.
Nuovo sovraccarico TimeSpan.FromMilliseconds con un singolo parametro
Il metodo TimeSpan.FromMilliseconds(Int64, Int64) è stato introdotto in precedenza senza aggiungere un overload che accetta un singolo parametro.
Anche se funziona perché il secondo parametro è facoltativo, genera un errore di compilazione quando viene usato in un'espressione LINQ, ad esempio:
Expression<Action> a = () => TimeSpan.FromMilliseconds(1000);
Il problema si verifica perché le espressioni LINQ non possono gestire parametri facoltativi. Per risolvere questo problema, .NET 10 introduce un nuovo overload che accetta un singolo parametro. Modifica anche il metodo esistente per rendere obbligatorio il secondo parametro.
Stringhe
- API di normalizzazione delle stringhe per operare con l'intervallo di caratteri
- Supporto UTF-8 per la conversione di stringhe esadecimale
API di normalizzazione delle stringhe da usare con l'intervallo di caratteri
La normalizzazione delle stringhe Unicode è stata supportata da molto tempo, ma le API esistenti funzionano solo con il tipo stringa. Ciò significa che i chiamanti con dati archiviati in forme diverse, ad esempio matrici di caratteri o intervalli, devono allocare una nuova stringa per usare queste API. Inoltre, le API che restituiscono una stringa normalizzata allocano sempre una nuova stringa per rappresentare l'output normalizzato.
.NET 10 introduce nuove API che funzionano con intervalli di caratteri, che espandono la normalizzazione oltre i tipi di stringa e consentono di evitare allocazioni non necessarie:
- StringNormalizationExtensions.GetNormalizedLength(ReadOnlySpan<Char>, NormalizationForm)
- StringNormalizationExtensions.IsNormalized(ReadOnlySpan<Char>, NormalizationForm)
- StringNormalizationExtensions.TryNormalize(ReadOnlySpan<Char>, Span<Char>, Int32, NormalizationForm)
Supporto UTF-8 per la conversione di stringa esadecimale
.NET 10 aggiunge il supporto UTF-8 per le operazioni di conversione di stringhe esadecimale nella Convert classe . Questi nuovi metodi offrono modi efficienti per eseguire la conversione tra sequenze di byte UTF-8 e rappresentazioni esadecimali senza richiedere allocazioni di stringhe intermedie:
- Convert.FromHexString(ReadOnlySpan<Byte>)
- Convert.FromHexString(ReadOnlySpan<Byte>, Span<Byte>, Int32, Int32)
- Convert.TryToHexString(ReadOnlySpan<Byte>, Span<Byte>, Int32)
- Convert.TryToHexStringLower(ReadOnlySpan<Byte>, Span<Byte>, Int32)
Questi metodi rispecchiano gli overload esistenti che funzionano con string e ReadOnlySpan<char>, ma operano direttamente in byte codificati UTF-8 per migliorare le prestazioni negli scenari in cui si lavora già con i dati UTF-8.
Collezioni
Sovraccarichi aggiuntivi per TryAdd e TryGetValue in OrderedDictionary<TKey, TValue>
OrderedDictionary<TKey,TValue> fornisce TryAdd e TryGetValue per l'aggiunta e il recupero come qualsiasi altra implementazione di IDictionary<TKey, TValue>. Esistono tuttavia scenari in cui è possibile eseguire altre operazioni, quindi vengono aggiunti nuovi overload che restituiscono un indice alla voce:
Questo indice può quindi essere usato con GetAt e SetAt per un accesso rapido all'entry. Un esempio di utilizzo del nuovo TryAdd overload consiste nell'aggiungere o aggiornare una coppia chiave-valore nel dizionario ordinato:
// Try to add a new key with value 1.
if (!orderedDictionary.TryAdd(key, 1, out int index))
{
// Key was present, so increment the existing value instead.
int value = orderedDictionary.GetAt(index).Value;
orderedDictionary.SetAt(index, value + 1);
}
Questa nuova API è già utilizzata in JsonObject e migliora le prestazioni degli aggiornamenti delle proprietà di 10-20%.
Serializzazione
-
Permetti di specificare ReferenceHandler in
JsonSourceGenerationOptions - Opzione per impedire le proprietà JSON duplicate
- Opzioni di serializzazione JSON rigorose
- Supporto di PipeReader per serializzatore JSON
Consenti di specificare ReferenceHandler in JsonSourceGenerationOptions
Quando si usano generatori di origine per la serializzazione JSON, il contesto generato genera un'eccezione quando i cicli vengono serializzati o deserializzati. Ora puoi personalizzare questo comportamento specificando il ReferenceHandler nel JsonSourceGenerationOptionsAttribute. Ecco un esempio che usa JsonKnownReferenceHandler.Preserve:
public static void MakeSelfRef()
{
SelfReference selfRef = new SelfReference();
selfRef.Me = selfRef;
Console.WriteLine(JsonSerializer.Serialize(selfRef, ContextWithPreserveReference.Default.SelfReference));
// Output: {"$id":"1","Me":{"$ref":"1"}}
}
[JsonSourceGenerationOptions(ReferenceHandler = JsonKnownReferenceHandler.Preserve)]
[JsonSerializable(typeof(SelfReference))]
internal partial class ContextWithPreserveReference : JsonSerializerContext
{
}
internal class SelfReference
{
public SelfReference Me { get; set; } = null!;
}
Opzione per impedire le proprietà JSON duplicate
La specifica JSON non specifica come gestire le proprietà duplicate durante la deserializzazione di un payload JSON. Ciò può causare risultati imprevisti e vulnerabilità di sicurezza. .NET 10 introduce l'opzione JsonSerializerOptions.AllowDuplicateProperties per impedire le proprietà JSON duplicate:
string json = """{ "Value": 1, "Value": -1 }""";
Console.WriteLine(JsonSerializer.Deserialize<MyRecord>(json).Value); // -1
JsonSerializerOptions options = new() { AllowDuplicateProperties = false };
JsonSerializer.Deserialize<MyRecord>(json, options); // throws JsonException
JsonSerializer.Deserialize<JsonObject>(json, options); // throws JsonException
JsonSerializer.Deserialize<Dictionary<string, int>>(json, options); // throws JsonException
JsonDocumentOptions docOptions = new() { AllowDuplicateProperties = false };
JsonDocument.Parse(json, docOptions); // throws JsonException
record MyRecord(int Value);
I duplicati vengono rilevati controllando se un valore viene assegnato più volte durante la deserializzazione, quindi funziona come previsto con altre opzioni come la distinzione tra maiuscole e minuscole e i criteri di denominazione.
Opzioni di serializzazione JSON rigorose
Il serializzatore JSON accetta molte opzioni per personalizzare la serializzazione e la deserializzazione, ma le impostazioni predefinite potrebbero essere troppo rilassate per alcune applicazioni. .NET 10 aggiunge un nuovo JsonSerializerOptions.Strict set di impostazioni che segue le procedure consigliate includendo le opzioni seguenti:
- Applica i JsonUnmappedMemberHandling.Disallow criteri.
- Disabilita JsonSerializerOptions.AllowDuplicateProperties.
- Mantiene l'associazione delle proprietà sensibile alla distinzione tra maiuscole e minuscole.
- Abilita sia JsonSerializerOptions.RespectNullableAnnotations che JsonSerializerOptions.RespectRequiredConstructorParameters le impostazioni.
Queste opzioni sono compatibili con la lettura: JsonSerializerOptions.Default un oggetto serializzato con JsonSerializerOptions.Default può essere deserializzato con JsonSerializerOptions.Strict.
Per altre informazioni sulla serializzazione JSON, vedere Panoramica di System.Text.Json.
Supporto di PipeReader per serializzatore JSON
JsonSerializer.Deserialize supporta PipeReaderora , completando il supporto esistente PipeWriter . In precedenza, la deserializzazione da una PipeReader richiesta di conversione in un Streamoggetto , ma i nuovi overload eliminano tale passaggio integrandolo PipeReader direttamente nel serializzatore. Come bonus, non dover convertire da quello che si sta già tenendo può produrre alcuni vantaggi di efficienza.
In questo modo viene illustrato l'utilizzo di base:
using System;
using System.IO.Pipelines;
using System.Text.Json;
using System.Threading.Tasks;
var pipe = new Pipe();
// Serialize to writer
await JsonSerializer.SerializeAsync(pipe.Writer, new Person("Alice"));
await pipe.Writer.CompleteAsync();
// Deserialize from reader
var result = await JsonSerializer.DeserializeAsync<Person>(pipe.Reader);
await pipe.Reader.CompleteAsync();
Console.WriteLine($"Your name is {result.Name}.");
// Output: Your name is Alice.
record Person(string Name);
Di seguito è riportato un esempio di producer che produce token in blocchi e un consumer che riceve e li visualizza:
using System;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Text.Json;
using System.Threading.Tasks;
var pipe = new Pipe();
// Producer writes to the pipe in chunks.
var producerTask = Task.Run(async () =>
{
async static IAsyncEnumerable<Chunk> GenerateResponse()
{
yield return new Chunk("The quick brown fox", DateTime.Now);
await Task.Delay(500);
yield return new Chunk(" jumps over", DateTime.Now);
await Task.Delay(500);
yield return new Chunk(" the lazy dog.", DateTime.Now);
}
await JsonSerializer.SerializeAsync<IAsyncEnumerable<Chunk>>(pipe.Writer, GenerateResponse());
await pipe.Writer.CompleteAsync();
});
// Consumer reads from the pipe and outputs to console.
var consumerTask = Task.Run(async () =>
{
var thinkingString = "...";
var clearThinkingString = new string("\b\b\b");
var lastTimestamp = DateTime.MinValue;
// Read response to end.
Console.Write(thinkingString);
await foreach (var chunk in JsonSerializer.DeserializeAsyncEnumerable<Chunk>(pipe.Reader))
{
Console.Write(clearThinkingString);
Console.Write(chunk.Message);
Console.Write(thinkingString);
lastTimestamp = DateTime.Now;
}
Console.Write(clearThinkingString);
Console.WriteLine($" Last message sent at {lastTimestamp}.");
await pipe.Reader.CompleteAsync();
});
await producerTask;
await consumerTask;
record Chunk(string Message, DateTime Timestamp);
Tutto questo viene serializzato come JSON in Pipe (formattato qui per la leggibilità):
[
{
"Message": "The quick brown fox",
"Timestamp": "2025-08-01T18:37:27.2930151-07:00"
},
{
"Message": " jumps over",
"Timestamp": "2025-08-01T18:37:27.8594502-07:00"
},
{
"Message": " the lazy dog.",
"Timestamp": "2025-08-01T18:37:28.3753669-07:00"
}
]
Sistema.Numerici
Altri metodi di trasformazione di matrice con mano sinistra
.NET 10 aggiunge le API rimanenti per la creazione di matrici di trasformazione a sinistra per le matrici di cartelloni pubblicitari e con vincoli. È possibile usare questi metodi come le versioni esistenti per destri, ad esempio CreateBillboard(Vector3, Vector3, Vector3, Vector3), quando si utilizza invece un sistema di coordinate mancino:
- Matrix4x4.CreateBillboardLeftHanded(Vector3, Vector3, Vector3, Vector3)
- Matrix4x4.CreateConstrainedBillboardLeftHanded(Vector3, Vector3, Vector3, Vector3, Vector3)
Miglioramenti del tensore
Lo System.Numerics.Tensors spazio dei nomi include ora un'interfaccia non generica, IReadOnlyTensor, per operazioni come l'accesso a Lengths e Strides. Le operazioni di sezione non copiano più i dati, migliorando le prestazioni. Inoltre, è possibile accedere ai dati in modo non generico tramite boxing a object quando le prestazioni non sono critiche.
Le API tensor sono ora stabili e non sono più contrassegnate come sperimentali. Anche se le API richiedono ancora di fare riferimento al pacchetto NuGet System.Numerics.Tensors , sono state esaminate e completate per la versione di .NET 10. I tipi sfruttano gli operatori di estensione C# 14 per fornire operazioni aritmetiche quando il tipo T sottostante supporta l'operazione. Se T implementa le interfacce matematiche generiche pertinenti, IAdditionOperators<TSelf, TOther, TResult> ad esempio o INumber<TSelf>, l'operazione è supportata. Ad esempio, tensor + tensor è disponibile per un Tensor<int>oggetto , ma non è disponibile per un oggetto Tensor<bool>.
Convalida delle opzioni
Nuovo costruttore sicuro AOT per ValidationContext
La ValidationContext classe , usata durante la convalida delle opzioni, include un nuovo overload del costruttore che accetta in modo esplicito il displayName parametro :
ValidationContext(Object, String, IServiceProvider, IDictionary<Object,Object>)
Il nome visualizzato garantisce la sicurezza AOT e ne abilita l'uso nelle compilazioni native senza avvisi.
Diagnostica
-
Supporto per gli URL dello schema di telemetria in
ActivitySourceeMeter - Supporto della traccia out-of-process per eventi e link di attività
- Supporto per il campionamento di traccia con limitazione di frequenza
Supporto per gli URL dello schema di telemetria in ActivitySource e Meter
ActivitySource e Meter ora supportano la specifica di un URL dello schema di telemetria durante la costruzione, allineato alle specifiche OpenTelemetry. Lo schema di telemetria garantisce coerenza e compatibilità per i dati di traccia e metriche. .NET 10 introduce ActivitySourceOptionsinoltre , che semplifica la creazione di istanze con più opzioni di configurazione (incluso ActivitySource di telemetria).
Le nuove API sono:
- ActivitySource(ActivitySourceOptions)
- ActivitySource.TelemetrySchemaUrl
- Meter.TelemetrySchemaUrl
- ActivitySourceOptions
Supporto della traccia fuori dal processo per eventi e collegamenti di attività
La Activity classe abilita la traccia distribuita monitorando il flusso delle operazioni tra servizi o componenti. .NET supporta la serializzazione di questi dati di traccia fuori dal processo tramite il provider sorgente di eventi Microsoft-Diagnostics-DiagnosticSource. Un Activity oggetto può includere metadati aggiuntivi, ad ActivityLink esempio e ActivityEvent. .NET 10 aggiunge il supporto per la serializzazione di questi collegamenti ed eventi, quindi i dati di traccia fuori dal processo ora includono tali informazioni. Per esempio:
Events->"[(TestEvent1,2025-03-27T23:34:10.6225721+00:00,[E11:EV1,E12:EV2]),(TestEvent2,2025-03-27T23:34:11.6276895+00:00,[E21:EV21,E22:EV22])]"
Links->"[(19b6e8ea216cb2ba36dd5d957e126d9f,98f7abcb3418f217,Recorded,null,false,[alk1:alv1,alk2:alv2]),(2d409549aadfdbdf5d1892584a5f2ab2,4f3526086a350f50,None,null,false)]"
Supporto per la limitazione del campionamento delle tracce in base alla frequenza
Quando i dati di traccia distribuiti vengono serializzati al di fuori del processo tramite il provider dell'origine degli eventi Microsoft-Diagnostics-DiagnosticSource, è possibile emettere tutte le attività registrate o applicare il campionamento in base a un rapporto di traccia.
Una nuova opzione di campionamento denominata Campionamento di limitazione della frequenza limita il numero di attività radice serializzate al secondo. Ciò consente di controllare più precisamente il volume dei dati.
Gli aggregatori di dati di traccia fuori dal processo possono abilitare e configurare questo campionamento specificando l'opzione in FilterAndPayloadSpecs. Ad esempio, l'impostazione seguente limita la serializzazione a 100 attività radice al secondo in tutte le ActivitySource istanze:
[AS]*/-ParentRateLimitingSampler(100)
File ZIP
- Miglioramenti delle prestazioni e della memoria zipArchive
- Nuove API ZIP asincrone
- Miglioramento delle prestazioni in GZipStream per i flussi concatenati
Miglioramenti delle prestazioni e della memoria zipArchive
.NET 10 migliora le prestazioni e l'utilizzo della memoria di ZipArchive.
In primo luogo, il processo di registrazione delle voci su un ZipArchive è stato ottimizzato quando si è in modalità Update. In precedenza, tutte le istanze di ZipArchiveEntry venivano caricate in memoria e riscritte, il che poteva causare un elevato utilizzo della memoria e colli di bottiglia nelle prestazioni. L'ottimizzazione riduce l'utilizzo della memoria e migliora le prestazioni evitando la necessità di caricare tutte le voci in memoria.
In secondo luogo, l'estrazione delle voci di ZipArchive è ora parallelizzata e le strutture di dati interne sono ottimizzate per un uso più efficiente della memoria. Questi miglioramenti consentono di risolvere i problemi relativi ai colli di bottiglia delle prestazioni e all'utilizzo elevato della memoria, rendendo ZipArchive più efficiente e veloce, soprattutto quando si gestiscono archivi di grandi dimensioni.
Nuove API ZIP asincrone
.NET 10 introduce nuove API asincrone che semplificano l'esecuzione di operazioni non bloccanti durante la lettura o la scrittura in file ZIP. Questa funzionalità è stata fortemente richiesta dalla community.
Sono disponibili nuovi async metodi per estrarre, creare e aggiornare gli archivi ZIP. Questi metodi consentono agli sviluppatori di gestire in modo efficiente file di grandi dimensioni e migliorare la velocità di risposta dell'applicazione, in particolare negli scenari che coinvolgono operazioni associate a I/O. Questi metodi includono:
- ZipArchive.CreateAsync(Stream, ZipArchiveMode, Boolean, Encoding, CancellationToken)
- ZipArchiveEntry.OpenAsync(CancellationToken)
- ZipFile.CreateFromDirectoryAsync
- ZipFile.ExtractToDirectoryAsync
- ZipFile.OpenAsync
- ZipFile.OpenReadAsync(String, CancellationToken)
- ZipFileExtensions.CreateEntryFromFileAsync
- ZipFileExtensions.ExtractToDirectoryAsync
- ZipFileExtensions.ExtractToFileAsync
Per esempi di uso di queste API, vedere il post di blog preview 4.
Miglioramento delle prestazioni in GZipStream per i flussi concatenati
Un contributo della community ha migliorato le prestazioni di GZipStream quando si elaborano flussi di dati GZip concatenati. In precedenza, ogni nuovo segmento di flusso eliminava e riallocava l'interno ZLibStreamHandle, il che comportava allocazioni di memoria aggiuntive e costi di inizializzazione aggiuntivi. Con questa modifica, l'handle viene ora reimpostato e riutilizzato per ridurre le allocazioni di memoria gestite e non gestite e migliorare il tempo di esecuzione. L'impatto maggiore (circa 35% più veloce) si verifica durante l'elaborazione di un numero elevato di flussi di dati di piccole dimensioni. Questa modifica:
- Elimina l'allocazione ripetuta di ~64-80 byte di memoria per ogni flusso concatenato, con risparmi aggiuntivi sulla memoria non gestita.
- Riduce il tempo di esecuzione di circa 400 ns per flusso concatenato.
Gestione dei processi di Windows
Avviare processi Windows in un nuovo gruppo di processi
Per Windows, è ora possibile usare ProcessStartInfo.CreateNewProcessGroup per avviare un processo in un gruppo di processi separato. In questo modo è possibile inviare segnali isolati ai processi figlio che altrimenti potrebbero arrestare l'elemento padre senza una corretta gestione. L'invio di segnali è utile per evitare la terminazione forzata.
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
class Program
{
static void Main(string[] args)
{
bool isChildProcess = args.Length > 0 && args[0] == "child";
if (!isChildProcess)
{
var psi = new ProcessStartInfo
{
FileName = Environment.ProcessPath,
Arguments = "child",
CreateNewProcessGroup = true,
};
using Process process = Process.Start(psi)!;
Thread.Sleep(5_000);
GenerateConsoleCtrlEvent(CTRL_C_EVENT, (uint)process.Id);
process.WaitForExit();
Console.WriteLine("Child process terminated gracefully, continue with the parent process logic if needed.");
}
else
{
// If you need to send a CTRL+C, the child process needs to re-enable CTRL+C handling, if you own the code, you can call SetConsoleCtrlHandler(NULL, FALSE).
// see https://learn.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw#remarks
SetConsoleCtrlHandler((IntPtr)null, false);
Console.WriteLine("Greetings from the child process! I need to be gracefully terminated, send me a signal!");
bool stop = false;
var registration = PosixSignalRegistration.Create(PosixSignal.SIGINT, ctx =>
{
stop = true;
ctx.Cancel = true;
Console.WriteLine("Received CTRL+C, stopping...");
});
StreamWriter sw = File.AppendText("log.txt");
int i = 0;
while (!stop)
{
Thread.Sleep(1000);
sw.WriteLine($"{++i}");
Console.WriteLine($"Logging {i}...");
}
// Clean up
sw.Dispose();
registration.Dispose();
Console.WriteLine("Thanks for not killing me!");
}
}
private const int CTRL_C_EVENT = 0;
private const int CTRL_BREAK_EVENT = 1;
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetConsoleCtrlHandler(IntPtr handler, [MarshalAs(UnmanagedType.Bool)] bool Add);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent, uint dwProcessGroupId);
}
Miglioramenti di WebSocket
WebSocketStream
.NET 10 introduce WebSocketStream, una nuova API progettata per semplificare alcuni degli scenari più comuni, e in precedenza complessi,WebSocket in .NET.
Le API tradizionali WebSocket sono di basso livello e richiedono un boilerplate significativo: gestione del buffering e frame, ricostruzione dei messaggi, gestione della codifica/decodifica e scrittura di wrapper personalizzati per l'integrazione con flussi, canali o altre astrazioni di trasporto. Queste complessità rendono difficile usare WebSocket come trasporto, soprattutto per le app con protocolli basati su streaming o testo o gestori basati su eventi.
WebSocketStream risolve questi punti di dolore fornendo un'astrazione Streambasata su un WebSocket. Ciò consente un'integrazione senza problemi con le API esistenti per la lettura, la scrittura e l'analisi dei dati, sia binari che di testo, e riduce la necessità di tubazioni manuali.
WebSocketStream abilita API di alto livello e familiari per modelli di consumo e produzione WebSocket comuni. Queste API riducono l'attrito e semplificano l'implementazione di scenari avanzati.
Modelli di utilizzo comuni
Ecco alcuni esempi di come WebSocketStream semplifica i flussi di lavoro tipici WebSocket :
Protocollo di testo di streaming (ad esempio, STOMP)
using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
// Streaming text protocol (for example, STOMP).
using Stream transportStream = WebSocketStream.Create(
connectedWebSocket,
WebSocketMessageType.Text,
ownsWebSocket: true);
// Integration with Stream-based APIs.
// Don't close the stream, as it's also used for writing.
using var transportReader = new StreamReader(transportStream, leaveOpen: true);
var line = await transportReader.ReadLineAsync(cancellationToken); // Automatic UTF-8 and new line handling.
transportStream.Dispose(); // Automatic closing handshake handling on `Dispose`.
Protocollo binario di streaming (ad esempio, AMQP)
using System;
using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
// Streaming binary protocol (for example, AMQP).
Stream transportStream = WebSocketStream.Create(
connectedWebSocket,
WebSocketMessageType.Binary,
closeTimeout: TimeSpan.FromSeconds(10));
await message.SerializeToStreamAsync(transportStream, cancellationToken);
var receivePayload = new byte[payloadLength];
await transportStream.ReadExactlyAsync(receivePayload, cancellationToken);
transportStream.Dispose();
// `Dispose` automatically handles closing handshake.
Leggere un singolo messaggio come flusso (ad esempio, deserializzazione JSON)
using System.IO;
using System.Net.WebSockets;
using System.Text.Json;
// Reading a single message as a stream (for example, JSON deserialization).
using Stream messageStream = WebSocketStream.CreateReadableMessageStream(connectedWebSocket, WebSocketMessageType.Text);
// JsonSerializer.DeserializeAsync reads until the end of stream.
var appMessage = await JsonSerializer.DeserializeAsync<AppMessage>(messageStream);
Scrivere un singolo messaggio come flusso (ad esempio, serializzazione binaria)
using System;
using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
// Writing a single message as a stream (for example, binary serialization).
public async Task SendMessageAsync(AppMessage message, CancellationToken cancellationToken)
{
using Stream messageStream = WebSocketStream.CreateWritableMessageStream(_connectedWebSocket, WebSocketMessageType.Binary);
foreach (ReadOnlyMemory<byte> chunk in message.SplitToChunks())
{
await messageStream.WriteAsync(chunk, cancellationToken);
}
} // EOM sent on messageStream.Dispose().
Miglioramenti di TLS
TLS 1.3 per macOS (client)
.NET 10 aggiunge il supporto TLS 1.3 sul lato client in macOS integrando Network.framework di Apple in SslStream e HttpClient. In passato macOS usava il trasporto sicuro che non supporta TLS 1.3; il consenso esplicito in Network.framework abilita TLS 1.3.
Ambito e comportamento
- solo macOS, lato client in questa versione.
- Acconsentire esplicitamente. Le app esistenti continuano a usare lo stack corrente, a meno che non sia abilitato.
- Se abilitata, le versioni precedenti di TLS (TLS 1.0 e 1.1) potrebbero non essere più disponibili tramite Network.framework.
Come abilitare
Usare un'opzione AppContext nel codice:
// Opt in to Network.framework-backed TLS on Apple platforms.
AppContext.SetSwitch("System.Net.Security.UseNetworkFramework", true);
using var client = new HttpClient();
var html = await client.GetStringAsync("https://example.com");
In alternativa, usare una variabile di ambiente:
# Opt-in via environment variable (set for the process or machine as appropriate)
DOTNET_SYSTEM_NET_SECURITY_USENETWORKFRAMEWORK=1
# or
DOTNET_SYSTEM_NET_SECURITY_USENETWORKFRAMEWORK=true
Note
- TLS 1.3 si applica alle SslStream API e basate su di essa (ad esempio,HttpClient/HttpMessageHandler ).
- I pacchetti di crittografia sono controllati da macOS tramite Network.framework.
- Il comportamento del flusso sottostante può essere diverso quando Network.framework è abilitato (ad esempio, buffering, completamento di lettura/scrittura, semantica di annullamento).
- La semantica può differire per le letture a zero byte. Evitare di basarsi sulle letture di lunghezza zero per rilevare la disponibilità dei dati.
- Alcuni nomi host IDN (Internationalized Domain Names) potrebbero essere rifiutati da Network.framework. Preferisce nomi host ASCII/Punycode (A-label) o convalidare i nomi rispetto ai vincoli macOS/Network.framework.
- Se l'app si basa su un comportamento specifico SslStream del caso perimetrale, convalidarlo in Network.framework.