Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Cet article décrit les nouvelles fonctionnalités des bibliothèques .NET pour .NET 9.
Base64Url
Base64 est un schéma d’encodage qui traduit des octets arbitraires en texte composé d’un ensemble spécifique de 64 caractères. Il s’agit d’une approche courante pour le transfert de données et a depuis longtemps été prise en charge par le biais de diverses méthodes, comme avec Convert.ToBase64String ou Base64.DecodeFromUtf8(ReadOnlySpan<Byte>, Span<Byte>, Int32, Int32, Boolean). Toutefois, certains des caractères qu’il utilise le rendent moins qu'idéal pour une utilisation dans certaines circonstances où vous pourriez autrement vouloir l'utiliser, comme dans les chaînes de requête. En particulier, les 64 caractères qui composent la table Base64 incluent « + » et « / », dont les deux ont leur propre signification dans les URL. Cela a conduit à la création du schéma Base64Url, qui est similaire à Base64, mais utilise un ensemble légèrement différent de caractères qui le rend approprié pour une utilisation dans les contextes d’URL. .NET 9 inclut la nouvelle Base64Url classe, qui fournit de nombreuses méthodes utiles et optimisées pour l’encodage et le décodage avec Base64Url de nombreux types de données.
L’exemple suivant illustre l’utilisation de la nouvelle classe.
ReadOnlySpan<byte> bytes = ...;
string encoded = Base64Url.EncodeToString(bytes);
BinaryFormatter
.NET 9 supprime BinaryFormatter du runtime .NET. Les API sont toujours présentes, mais leurs implémentations lèvent toujours une exception, quel que soit le type de projet. Pour plus d’informations sur la suppression et vos options si vous êtes affecté, consultez le guide de migration BinaryFormatter.
Collections
Les types de collection dans .NET obtiennent les mises à jour suivantes pour .NET 9 :
- Recherches de collection avec étendues
OrderedDictionary<TKey, TValue>- La méthode PriorityQueue.Remove() vous permet de mettre à jour la priorité d’un élément dans la file d’attente.
ReadOnlySet<T>
Recherches de collection avec étendues
Dans le code hautes performances, les étendues sont souvent utilisées pour éviter d’allouer inutilement des chaînes, et les tables de recherche avec des types comme Dictionary<TKey,TValue> et HashSet<T> sont fréquemment utilisées comme caches. Toutefois, il n’existe pas de mécanisme intégré sûr pour effectuer des recherches sur ces types de collections avec des étendues. Avec la nouvelle allows ref struct fonctionnalité de C# 13 et les nouveautés concernant ces types de collection dans .NET 9, il est désormais possible d’effectuer ces types de recherches.
L’exemple suivant illustre l’utilisation de Dictionary<TKey,TValue>.GetAlternateLookup.
static Dictionary<string, int> CountWords(ReadOnlySpan<char> input)
{
Dictionary<string, int> wordCounts = new(StringComparer.OrdinalIgnoreCase);
Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>> spanLookup =
wordCounts.GetAlternateLookup<ReadOnlySpan<char>>();
foreach (Range wordRange in Regex.EnumerateSplits(input, @"\b\W+"))
{
if (wordRange.Start.Value == wordRange.End.Value)
{
continue; // Skip empty ranges.
}
ReadOnlySpan<char> word = input[wordRange];
spanLookup[word] = spanLookup.TryGetValue(word, out int count) ? count + 1 : 1;
}
return wordCounts;
}
OrderedDictionary<TKey, TValue>
Dans de nombreux scénarios, vous pouvez stocker des paires clé-valeur d’une manière où l’ordre peut être conservé (liste de paires clé-valeur), mais où la recherche rapide par clé est également prise en charge (un dictionnaire de paires clé-valeur). Depuis les premiers jours de .NET, le OrderedDictionary type a pris en charge ce scénario, mais uniquement de manière non générique, avec des clés et des valeurs typées comme object. .NET 9 introduit la collection à longue demande OrderedDictionary<TKey,TValue> , qui fournit un type générique efficace pour prendre en charge ces scénarios.
Le code suivant utilise la nouvelle classe.
OrderedDictionary<string, int> d = new()
{
["a"] = 1,
["b"] = 2,
["c"] = 3,
};
d.Add("d", 4);
d.RemoveAt(0);
d.RemoveAt(2);
d.Insert(0, "e", 5);
foreach (KeyValuePair<string, int> entry in d)
{
Console.WriteLine(entry);
}
// Output:
// [e, 5]
// [b, 2]
// [c, 3]
Méthode PriorityQueue.Remove()
.NET 6 a introduit la collection PriorityQueue<TElement,TPriority>, qui fournit une implémentation simple et rapide de tableau/tas. L’un des problèmes liés aux tas de tableaux en général est qu’ils ne prennent pas en charge les mises à jour prioritaires, ce qui les rend prohibitifs à utiliser dans des algorithmes tels que des variantes de l’algorithme de Dijkstra.
Bien qu’il ne soit pas possible d’implémenter des mises à jour de priorité $O(\log n)$ efficaces dans la collection existante, la nouvelle PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) méthode permet d’émuler les mises à jour prioritaires (même si elles sont à $O(n)$ temps) :
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);
}
Cette méthode débloque les utilisateurs qui souhaitent implémenter des algorithmes de graphe dans des contextes où les performances asymptotiques ne sont pas un bloqueur. (Ces contextes incluent l’éducation et le prototypage.) Par exemple, voici une implémentation de l’algorithme de Dijkstra qui utilise la nouvelle API.
ReadOnlySet<T>
Il est souvent souhaitable de donner des accès en lecture seule aux collections. ReadOnlyCollection<T> vous permet de créer un wrapper en lecture seule autour d’un mutable IList<T>arbitraire et ReadOnlyDictionary<TKey,TValue> de créer un wrapper en lecture seule autour d’un mutable IDictionary<TKey,TValue>arbitraire. Toutefois, les versions antérieures de .NET n’avaient pas de prise en charge intégrée pour faire de même avec ISet<T>. .NET 9 introduit ReadOnlySet<T> pour résoudre ce problème.
La nouvelle classe active le modèle d’utilisation suivant.
private readonly HashSet<int> _set = [];
private ReadOnlySet<int>? _setWrapper;
public ReadOnlySet<int> Set => _setWrapper ??= new(_set);
Modèle de composant : prise en charge du découpage TypeDescriptor
System.ComponentModel inclut de nouvelles API compatibles avec le découpage que vous pouvez activer afin de décrire les composants. Toute application, en particulier les applications tronquées autonomes, peut utiliser ces nouvelles API pour prendre en charge les scénarios de découpage.
L’API principale est la TypeDescriptor.RegisterType méthode de la TypeDescriptor classe. Cette méthode a l’attribut DynamicallyAccessedMembersAttribute afin que le découpage conserve les membres de ce type. Vous devez appeler cette méthode une fois par type, et idéalement dès le début.
Les API secondaires ont un FromRegisteredType suffixe, tel que TypeDescriptor.GetPropertiesFromRegisteredType(Type). Contrairement à leurs équivalents qui n'ont pas le FromRegisteredType suffixe, ces API n'ont pas d'attributs de découpage [RequiresUnreferencedCode] ou [DynamicallyAccessedMembers]. Le manque d’attributs de découpage aide les consommateurs à ne plus avoir à :
- Supprimez les avertissements de découpage, qui peuvent être risqués.
- Propager un paramètre
Typefortement typé vers d’autres méthodes peut être fastidieux ou irréalisable.
public static void RunIt()
{
// The Type from typeof() is passed to a different method.
// The trimmer doesn't know about ExampleClass anymore
// and thus there will be warnings when trimming.
Test(typeof(ExampleClass));
Console.ReadLine();
}
private static void Test(Type type)
{
// When publishing self-contained + trimmed,
// this line produces warnings IL2026 and IL2067.
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(type);
// When publishing self-contained + trimmed,
// the property count is 0 here instead of 2.
Console.WriteLine($"Property count: {properties.Count}");
// To avoid the warning and ensure reflection
// can see the properties, register the type:
TypeDescriptor.RegisterType<ExampleClass>();
// Get properties from the registered type.
properties = TypeDescriptor.GetPropertiesFromRegisteredType(type);
Console.WriteLine($"Property count: {properties.Count}");
}
public class ExampleClass
{
public string? Property1 { get; set; }
public int Property2 { get; set; }
}
Pour plus d’informations, consultez la proposition d’API.
Cryptographie
- Méthode CryptographicOperations.HashData()
- Algorithme KMAC
- AES-GCM et les algorithmes ChaChaPoly1305 activés pour iOS/tvOS/MacCatalyst
- Chargement de certificat X.509
- Support des fournisseurs OpenSSL
- Sécurité basée sur la virtualisation Windows CNG
Méthode CryptographicOperations.HashData()
.NET inclut plusieurs implémentations statiques « one-shot » des fonctions de hachage et des fonctions associées. Ces API incluent SHA256.HashData et HMACSHA256.HashData. Les API one-shot sont préférables à utiliser, car elles peuvent fournir les meilleures performances possibles et réduire ou éliminer les allocations.
Si un développeur souhaite fournir une API qui prend en charge le hachage où l’appelant définit l’algorithme de hachage à utiliser, il est généralement effectué en acceptant un HashAlgorithmName argument. Toutefois, l’utilisation de ce modèle avec des API à utilisation unique nécessite de basculer chaque HashAlgorithmName possible, puis d’utiliser la méthode appropriée. Pour résoudre ce problème, .NET 9 introduit l’API CryptographicOperations.HashData . Cette API vous permet de produire un hachage ou un HMAC sur une entrée sous la forme d’une capture unique où l’algorithme utilisé est déterminé par un HashAlgorithmName.
static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data)
{
byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data);
ProcessHash(hash);
}
Algorithme KMAC
.NET 9 fournit l’algorithme KMAC tel que spécifié par NIST SP-800-185. KECCAK Message Authentication Code (KMAC) est une fonction pseudo-random et une fonction de hachage clé basée sur KECCAK.
Les nouvelles classes suivantes utilisent l’algorithme KMAC. Utilisez des instances pour accumuler des données afin de produire un MAC ou utilisez la méthode statique HashData pour une exécution unique sur une seule entrée.
KMAC est disponible sur Linux avec OpenSSL 3.0 ou version ultérieure, et sur Windows 11 Build 26016 ou version ultérieure. Vous pouvez utiliser la propriété statique IsSupported pour déterminer si la plateforme prend en charge l’algorithme souhaité.
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.
}
AES-GCM et les algorithmes ChaChaPoly1305 activés pour iOS/tvOS/MacCatalyst
IsSupported et ChaChaPoly1305.IsSupported renvoient maintenant l’attribut true lors de l’exécution sur iOS 13+, tvOS 13+ et Mac Catalyst.
AesGcm prend uniquement en charge les valeurs d’étiquettes 16 octets (128 bits) sur les systèmes d’exploitation Apple.
Chargement de certificat X.509
Depuis .NET Framework 2.0, la façon de charger un certificat a été new X509Certificate2(bytes). Il y a également eu d’autres modèles, tels que new X509Certificate2(bytes, password, flags), new X509Certificate2(path), new X509Certificate2(path, password, flags), et X509Certificate2Collection.Import(bytes, password, flags) (et ses surcharges).
Ces méthodes utilisaient tous le sniffing de contenu pour déterminer si l’entrée pouvait être gérée, puis chargée si elle pouvait être gérée. Pour certains appelants, cette stratégie était très pratique. Mais il présente également des problèmes :
- Chaque format de fichier ne fonctionne pas sur chaque système d’exploitation.
- C’est un écart de protocole.
- C’est une source de problèmes de sécurité.
.NET 9 introduit une nouvelle X509CertificateLoader classe, qui a une conception « une méthode, un seul objectif ». Dans sa version initiale, il ne prend en charge que deux des cinq formats pris en charge par le X509Certificate2 constructeur. Il s’agit des deux formats qui ont fonctionné sur tous les systèmes d’exploitation.
Prise en charge des fournisseurs OpenSSL
.NET 8 a introduit les API OpenPrivateKeyFromEngine(String, String) spécifiques à OpenSSL et OpenPublicKeyFromEngine(String, String). Ils permettent d’interagir avec les composants OpenSSL ENGINE et d’utiliser des modules de sécurité matériels (HSM), par exemple.
.NET 9 introduit SafeEvpPKeyHandle.OpenKeyFromProvider(String, String), qui permet d’utiliser des fournisseurs OpenSSL et d’interagir avec des fournisseurs tels que tpm2 ou pkcs11.
Certaines distributions ont supprimé de la prise en chargeENGINE, car il est désormais obsolète.
L’extrait de code suivant montre l’utilisation de base :
byte[] data = [ /* example data */ ];
// Refer to your provider documentation, for example, https://github.com/tpm2-software/tpm2-openssl/tree/master.
using (SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("tpm2", "handle:0x81000007"))
using (ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle))
{
byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256);
// Do stuff with signature created by TPM.
}
Des améliorations ont été apportées aux performances lors de la négociation TLS, ainsi qu’aux interactions avec des clés privées RSA qui utilisent des composants ENGINE.
Sécurité basée sur la virtualisation Windows CNG
Windows 11 a ajouté de nouvelles API pour sécuriser les clés Windows avec la sécurité basée sur la virtualisation (VBS). Avec cette nouvelle fonctionnalité, les clés peuvent être protégées contre les attaques de vol de clés au niveau de l’administrateur avec un effet négligeable sur les performances, la fiabilité ou l’échelle.
.NET 9 a ajouté des indicateurs correspondants CngKeyCreationOptions . Les trois indicateurs suivants ont été ajoutés :
- Correspondance de
CngKeyCreationOptions.PreferVbsavecNCRYPT_PREFER_VBS_FLAG - Correspondance de
CngKeyCreationOptions.RequireVbsavecNCRYPT_REQUIRE_VBS_FLAG - Correspondance de
CngKeyCreationOptions.UsePerBootKeyavecNCRYPT_USE_PER_BOOT_KEY_FLAG
L’extrait de code suivant montre comment utiliser l’un des indicateurs :
using System.Security.Cryptography;
CngKeyCreationParameters cngCreationParams = new()
{
Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider,
KeyCreationOptions = CngKeyCreationOptions.RequireVbs | CngKeyCreationOptions.OverwriteExistingKey,
};
using (CngKey key = CngKey.Create(CngAlgorithm.ECDsaP256, "myKey", cngCreationParams))
using (ECDsaCng ecdsa = new ECDsaCng(key))
{
// Do stuff with the key.
}
Date et heure : nouvelles surcharges TimeSpan.From*
La TimeSpan classe propose plusieurs From* méthodes qui vous permettent de créer un TimeSpan objet à l’aide d’un double. Toutefois, étant double donné qu’il s’agit d’un format à virgule flottante binaire, l’imprécision inhérente peut entraîner des erreurs. Par exemple, TimeSpan.FromSeconds(101.832) peut ne pas représenter 101 seconds, 832 millisecondsprécisément , mais plutôt approximativement 101 seconds, 831.9999999999936335370875895023345947265625 milliseconds. Cette différence a provoqué une confusion fréquente et n’est pas non plus la façon la plus efficace de représenter ces données. Pour résoudre ce problème, .NET 9 ajoute de nouvelles surcharges qui vous permettent de créer TimeSpan des objets à partir d’entiers. Il y a de nouvelles surcharges de FromDays, FromHours, FromMinutes, FromSeconds, FromMilliseconds et FromMicroseconds.
Le code suivant montre un exemple d’appel de double et de l’une des nouvelles surcharges d’entiers.
TimeSpan timeSpan1 = TimeSpan.FromSeconds(value: 101.832);
Console.WriteLine($"timeSpan1 = {timeSpan1}");
// timeSpan1 = 00:01:41.8319999
TimeSpan timeSpan2 = TimeSpan.FromSeconds(seconds: 101, milliseconds: 832);
Console.WriteLine($"timeSpan2 = {timeSpan2}");
// timeSpan2 = 00:01:41.8320000
Injection de dépendance : constructeur ActivatorUtilities.CreateInstance
La résolution du constructeur pour ActivatorUtilities.CreateInstance a changé dans .NET 9. Auparavant, un constructeur qui était explicitement marqué à l’aide de l’attribut ActivatorUtilitiesConstructorAttribute peut ne pas être appelé, en fonction de l’ordre des constructeurs et du nombre de paramètres de constructeur. La logique a changé dans .NET 9 de sorte qu’un constructeur qui a l’attribut est toujours appelé.
Diagnostiques
- Debug.Assert signale la condition d’assertion par défaut
- Nouvelle méthode Activity.AddLink
- Instrument Metrics.Gauge
- Écoute de caractère générique du compteur hors processus
Debug.Assert signale la condition d’assertion par défaut
Debug.Assert est couramment utilisé pour aider à valider les conditions qui sont censées toujours être vraies. L’échec indique généralement un bogue dans le code. Il existe de nombreuses surcharges de Debug.Assert, dont la plus simple accepte simplement une condition :
Debug.Assert(a > 0 && b > 0);
L’assertion échoue si la condition est false. Historiquement, cependant, ces assertions n'étaient pas accompagnées d'informations sur la condition qui a échoué. À compter de .NET 9, si aucun message n’est explicitement fourni par l’utilisateur, l’assertion contient la représentation textuelle de la condition. Par exemple, pour l’exemple d’assertion précédent, plutôt que d’obtenir un message tel que :
Process terminated. Assertion failed.
at Program.SomeMethod(Int32 a, Int32 b)
Le message serait maintenant :
Process terminated. Assertion failed.
a > 0 && b > 0
at Program.SomeMethod(Int32 a, Int32 b)
Nouvelle méthode Activity.AddLink
Auparavant, vous ne pouviez lier un suivi Activity qu’à d’autres contextes de suivi lorsque vous avez créé le Activity. Dans .NET 9, l’API AddLink(ActivityLink) vous permet de lier un Activity objet à d’autres contextes de suivi après sa création. Cette modification s’aligne également sur les spécifications OpenTelemetry .
ActivityContext activityContext = new(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None);
ActivityLink activityLink = new(activityContext);
Activity activity = new("LinkTest");
activity.AddLink(activityLink);
Instrument Metrics.Gauge
System.Diagnostics.Metrics fournit désormais l’instrument Gauge<T> conformément à la spécification OpenTelemetry. L’instrument Gauge est conçu pour enregistrer des valeurs non additives lorsque des modifications se produisent. Par exemple, il peut mesurer le niveau de bruit de fond, où la somme des valeurs de plusieurs salles serait insensée. L’instrument Gauge est un type générique qui peut enregistrer n’importe quel type valeur, tel que int, doubleou decimal.
L’exemple suivant illustre l’utilisation de l’instrument Gauge .
Meter soundMeter = new("MeasurementLibrary.Sound");
Gauge<int> gauge = soundMeter.CreateGauge<int>(
name: "NoiseLevel",
unit: "dB", // Decibels.
description: "Background Noise Level"
);
gauge.Record(10, new TagList() { { "Room1", "dB" } });
Écoute de caractère générique du compteur hors processus
Il est déjà possible d’écouter des compteurs hors processus à l’aide du System.Diagnostics.Metrics fournisseur de source d’événements, mais avant .NET 9, vous deviez spécifier le nom entier du compteur. Dans .NET 9, vous pouvez écouter tous les compteurs à l’aide du caractère générique *, ce qui vous permet de capturer des données métriques de chaque compteur dans un processus. En outre, il ajoute la prise en charge de l’écoute par préfixe de compteur, de sorte que vous pouvez écouter tous les compteurs dont les noms commencent par un préfixe spécifié. Par exemple, la spécification MyMeter* permet d’écouter tous les compteurs avec des noms commençant par MyMeter.
// The complete meter name is "MyCompany.MyMeter".
var meter = new Meter("MyCompany.MyMeter");
// Create a counter and allow publishing values.
meter.CreateObservableCounter("MyCounter", () => 1);
// Create the listener to use the wildcard character
// to listen to all meters using prefix names.
MyEventListener listener = new MyEventListener();
La MyEventListener classe est définie comme suit.
internal class MyEventListener : EventListener
{
protected override void OnEventSourceCreated(EventSource eventSource)
{
Console.WriteLine(eventSource.Name);
if (eventSource.Name == "System.Diagnostics.Metrics")
{
// Listen to all meters with names starting with "MyCompany".
// If using "*", allow listening to all meters.
EnableEvents(
eventSource,
EventLevel.Informational,
(EventKeywords)0x3,
new Dictionary<string, string?>() { { "Metrics", "MyCompany*" } }
);
}
}
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
// Ignore other events.
if (eventData.EventSource.Name != "System.Diagnostics.Metrics" ||
eventData.EventName == "CollectionStart" ||
eventData.EventName == "CollectionStop" ||
eventData.EventName == "InstrumentPublished"
)
return;
Console.WriteLine(eventData.EventName);
if (eventData.Payload is not null)
{
for (int i = 0; i < eventData.Payload.Count; i++)
Console.WriteLine($"\t{eventData.PayloadNames![i]}: {eventData.Payload[i]}");
}
}
}
Lorsque vous exécutez le code, la sortie est la suivante :
CounterRateValuePublished
sessionId: 7cd94a65-0d0d-460e-9141-016bf390d522
meterName: MyCompany.MyMeter
meterVersion:
instrumentName: MyCounter
unit:
tags:
rate: 0
value: 1
instrumentId: 1
CounterRateValuePublished
sessionId: 7cd94a65-0d0d-460e-9141-016bf390d522
meterName: MyCompany.MyMeter
meterVersion:
instrumentName: MyCounter
unit:
tags:
rate: 0
value: 1
instrumentId: 1
Vous pouvez également utiliser le caractère générique pour écouter les métriques avec des outils de surveillance tels que dotnet-counters.
LINQ
De nouvelles méthodes CountBy et AggregateBy ont été introduites. Ces méthodes permettent d’agréger l’état par clé sans avoir besoin d’allouer des regroupements intermédiaires via GroupBy.
CountBy vous permet de calculer rapidement la fréquence de chaque clé. L’exemple suivant recherche le mot qui se produit le plus fréquemment dans une chaîne de texte.
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 vous permet d’implémenter des flux de travail à usage général. L’exemple suivant montre comment calculer les scores associés à une clé donnée.
(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>) permet d’extraire rapidement l’index implicite d’une énumération. Vous pouvez maintenant écrire du code tel que l’extrait de code suivant pour indexer automatiquement les éléments d’une collection.
IEnumerable<string> lines2 = File.ReadAllLines("output.txt");
foreach ((int index, string line) in lines2.Index())
{
Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}
Générateur de source de journalisation
C# 12 a introduit des constructeurs principaux, ce qui vous permet de définir un constructeur directement sur la déclaration de classe. Le générateur de source de journalisation prend désormais en charge la journalisation à l’aide de classes qui ont un constructeur principal.
public partial class ClassWithPrimaryConstructor(ILogger logger)
{
[LoggerMessage(0, LogLevel.Debug, "Test.")]
public partial void Test();
}
Divers
Dans cette section, recherchez des informations sur les points suivants :
allows ref struct utilisé dans les bibliothèques
C# 13 introduit la possibilité de limiter un paramètre générique avec allows ref struct, qui indique au compilateur et au runtime qu’un ref struct peut être utilisé pour ce paramètre générique. De nombreuses API compatibles avec cela ont été annotées. Par exemple, la String.Create méthode a une surcharge qui vous permet de créer un string en écrivant directement dans sa mémoire, représentée sous forme d’étendue. Cette méthode possède un argument TState transmis de l'appelant au délégué qui réalise l'écriture.
Ce TState paramètre de type sur String.Create est maintenant annoté avec allows ref struct.
public static string Create<TState>(int length, TState state, SpanAction<char, TState> action)
where TState : allows ref struct;
Cette annotation vous permet de passer une étendue (ou tout autre ref struct) comme entrée à cette méthode.
L’exemple suivant montre une nouvelle surcharge String.ToLowerInvariant() qui utilise cette fonctionnalité.
public static string ToLowerInvariant(ReadOnlySpan<char> input) =>
string.Create(span.Length, input, static (stringBuffer, input) => span.ToLowerInvariant(stringBuffer));
Expansion de SearchValues
.NET 8 a introduit le type SearchValues<T>, qui fournit une solution optimisée pour rechercher des ensembles spécifiques de caractères ou d’octets dans des plages. Dans .NET 9, SearchValues a été étendu pour prendre en charge la recherche de sous-chaînes dans une chaîne plus grande.
L’exemple suivant recherche plusieurs noms d’animaux dans une valeur de chaîne et retourne un index au premier trouvé.
private static readonly SearchValues<string> s_animals =
SearchValues.Create(["cat", "mouse", "dog", "dolphin"], StringComparison.OrdinalIgnoreCase);
public static int IndexOfAnimal(string text) =>
text.AsSpan().IndexOfAny(s_animals);
Cette nouvelle fonctionnalité a une implémentation optimisée qui tire parti de la prise en charge SIMD dans la plateforme sous-jacente. Il permet également d’optimiser les types de niveau supérieur. Par exemple, Regex utilise maintenant cette fonctionnalité dans le cadre de son implémentation.
Réseautage
- SocketsHttpHandler est par défaut dans HttpClientFactory
- System.Net.ServerSentEvents
- Reprise de session TLS avec certificats clients sur Linux
- Ping et expiration de l’activité de WebSocket
- HttpClientFactory ne journalise plus les valeurs d’en-tête par défaut
SocketsHttpHandler est par défaut dans HttpClientFactory
HttpClientFactory crée par défaut des objets HttpClient soutenus par HttpClientHandler.
HttpClientHandler est lui-même soutenu par SocketsHttpHandler, qui est beaucoup plus configurable, y compris autour de la gestion de la durée de vie des connexions.
HttpClientFactory utilise SocketsHttpHandler désormais par défaut et le configure pour définir des limites sur ses durées de vie de connexion pour qu’elles correspondent à celles de la durée de vie de rotation spécifiée dans la fabrique.
System.Net.ServerSentEvents
Les événements envoyés par le serveur (SSE) sont un protocole simple et populaire pour la diffusion de données d’un serveur vers un client. Il est utilisé, par exemple, par OpenAI dans le cadre de la diffusion en continu de texte généré à partir de ses services IA. Pour simplifier la consommation d’SSE, la nouvelle System.Net.ServerSentEvents bibliothèque fournit un analyseur permettant d’ingérer facilement les événements envoyés par le serveur.
Le code suivant illustre l’utilisation de la nouvelle classe.
Stream responseStream = new MemoryStream();
await foreach (SseItem<string> e in SseParser.Create(responseStream).EnumerateAsync())
{
Console.WriteLine(e.Data);
}
Reprise TLS avec des certificats clients sur Linux
TLS resume est une fonctionnalité du protocole TLS qui permet de reprendre des sessions établies précédemment sur un serveur. Cela évite quelques allers-retours et économise des ressources de calcul pendant l’établissement d’une liaison TLS.
La reprise TLS a déjà été prise en charge sur Linux pour les connexions SslStream sans certificats clients. .NET 9 ajoute la prise en charge de la reprise de session TLS des connexions TLS mutuellement authentifiées, qui sont courantes dans les scénarios serveur à serveur. La fonctionnalité est activée automatiquement.
Ping et expiration de l’activité de WebSocket
Les nouvelles API sur ClientWebSocketOptions et WebSocketCreationOptions vous permettent d’opter pour l'envoi de WebSocket pings et d'annuler la connexion si l'homologue ne répond pas dans le délai imparti.
Depuis maintenant, vous pouvez spécifier un KeepAliveInterval pour éviter que la connexion ne soit inactive, mais il n’y a pas eu de mécanisme intégré pour garantir que l'homologue réponde.
L’exemple suivant effectue un test ping sur le serveur toutes les 5 secondes et abandonne la connexion s’il ne répond pas au cours d’une seconde.
using var cws = new ClientWebSocket();
cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
cws.Options.KeepAliveInterval = TimeSpan.FromSeconds(5);
cws.Options.KeepAliveTimeout = TimeSpan.FromSeconds(1);
await cws.ConnectAsync(uri, httpClient, cancellationToken);
HttpClientFactory ne journalise plus les valeurs d’en-tête par défaut
LogLevel.Trace les événements enregistrés par HttpClientFactory n’incluent plus par défaut les valeurs d’en-tête. Vous pouvez choisir de journaliser des valeurs pour des en-têtes spécifiques via la méthode d’assistance RedactLoggedHeaders .
L’exemple suivant réacte tous les en-têtes, à l’exception de l’agent utilisateur.
services.AddHttpClient("myClient")
.RedactLoggedHeaders(name => name != "User-Agent");
Pour plus d’informations, consultez la section intitulée La journalisation de HttpClientFactory masque par défaut les valeurs d’en-tête.
Réflexion
Assemblies persistants
Dans les versions de .NET Core et .NET 5-8, la prise en charge de la création d’un assembly et l’émission de métadonnées de réflexion pour les types créés dynamiquement était limitée à un runnable AssemblyBuilder. Le manque de prise en charge de la sauvegarde d’un assembly était souvent un obstacle pour les clients qui migraient de .NET Framework vers .NET. .NET 9 ajoute un nouveau type, PersistedAssemblyBuilderque vous pouvez utiliser pour enregistrer un assembly émis.
Pour créer une PersistedAssemblyBuilder instance, appelez son constructeur et transmettez le nom de l’assembly, l’assembly principal, System.Private.CoreLibpour référencer les types de runtime de base et les attributs personnalisés facultatifs. Une fois que vous avez émis tous les membres à l'assemblage, appelez la méthode PersistedAssemblyBuilder.Save(String) pour créer un assemblage avec les paramètres par défaut. Si vous souhaitez définir le point d’entrée ou d’autres options, vous pouvez appeler PersistedAssemblyBuilder.GenerateMetadata et utiliser les métadonnées qu’il retourne pour enregistrer l’assembly. Le code suivant montre un exemple de création d’un assembly persistant et de définition du point d’entrée.
public void CreateAndSaveAssembly(string assemblyPath)
{
PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(
new AssemblyName("MyAssembly"),
typeof(object).Assembly
);
TypeBuilder tb = ab.DefineDynamicModule("MyModule")
.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder entryPoint = tb.DefineMethod(
"Main",
MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static
);
ILGenerator il = entryPoint.GetILGenerator();
// ...
il.Emit(OpCodes.Ret);
tb.CreateType();
MetadataBuilder metadataBuilder = ab.GenerateMetadata(
out BlobBuilder ilStream,
out BlobBuilder fieldData
);
PEHeaderBuilder peHeaderBuilder = new PEHeaderBuilder(
imageCharacteristics: Characteristics.ExecutableImage);
ManagedPEBuilder peBuilder = new ManagedPEBuilder(
header: peHeaderBuilder,
metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
ilStream: ilStream,
mappedFieldData: fieldData,
entryPoint: MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken)
);
BlobBuilder peBlob = new BlobBuilder();
peBuilder.Serialize(peBlob);
using var fileStream = new FileStream("MyAssembly.exe", FileMode.Create, FileAccess.Write);
peBlob.WriteContentTo(fileStream);
}
public static 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]));
}
La nouvelle PersistedAssemblyBuilder classe inclut la prise en charge de PDB. Vous pouvez émettre des informations de symbole et les utiliser pour déboguer un assemblage généré. L’API a une forme similaire à l’implémentation du .NET Framework. Pour plus d’informations, consultez Émettre des symboles et générer une base de données PDB.
Analyse de nom de type
TypeName est un analyseur pour les noms de type ECMA-335 qui fournit une fonctionnalité similaire à celle de System.Type, mais est découplé de l’environnement d’exécution. Les composants tels que les sérialiseurs et les compilateurs doivent analyser et traiter les noms de types. Par exemple, le compilateur AOT natif a basculé vers l’utilisation TypeName.
La nouvelle TypeName classe fournit les éléments suivants :
Méthodes statiques
ParseetTryParsepour l’analyse des entrées représentées en tant queReadOnlySpan<char>. Les deux méthodes acceptent une instance deTypeNameParseOptionsclasse (un sac d’options) qui vous permet de personnaliser l’analyse.Name,FullNameetAssemblyQualifiedNameles propriétés qui fonctionnent exactement comme leurs équivalents dans System.Type.Plusieurs propriétés et méthodes qui fournissent des informations supplémentaires sur le nom lui-même :
-
IsArray,IsSZArray(SZsignifie un tableau à dimension unique, un tableau indexé zéro),IsVariableBoundArrayTypeetGetArrayRankpour l’utilisation de tableaux. -
IsConstructedGenericType,GetGenericTypeDefinitionetGetGenericArgumentspour utiliser des noms de types génériques. -
IsByRefetIsPointerpour utiliser des pointeurs et des références managées. -
GetElementType()pour utiliser des pointeurs, des références et des tableaux. -
IsNestedetDeclaringTypepour utiliser des types imbriqués. -
AssemblyName, qui expose les informations de nom d’assembly via la nouvelle AssemblyNameInfo classe. Contrairement àAssemblyName, le nouveau type est immuable et l’analyse des noms de culture ne crée pas d’instances deCultureInfo.
-
Les deux TypeName types AssemblyNameInfo sont immuables et ne fournissent pas de moyen de vérifier l’égalité (ils n’implémentent IEquatablepas). La comparaison des noms d’assembly est simple, mais dans différents scénarios, il faut comparer uniquement un sous-ensemble d’informations exposées (Name, Version, CultureName, et PublicKeyOrToken).
L’extrait de code suivant montre un exemple d’utilisation.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
internal class RestrictedSerializationBinder
{
Dictionary<string, Type> AllowList { get; set; }
RestrictedSerializationBinder(Type[] allowedTypes)
=> AllowList = allowedTypes.ToDictionary(type => type.FullName!);
Type? GetType(ReadOnlySpan<char> untrustedInput)
{
if (!TypeName.TryParse(untrustedInput, out TypeName? parsed))
{
throw new InvalidOperationException($"Invalid type name: '{untrustedInput.ToString()}'");
}
if (AllowList.TryGetValue(parsed.FullName, out Type? type))
{
return type;
}
else if (parsed.IsSimple // It's not generic, pointer, reference, or an array.
&& parsed.AssemblyName is not null
&& parsed.AssemblyName.Name == "MyTrustedAssembly"
)
{
return Type.GetType(parsed.AssemblyQualifiedName, throwOnError: true);
}
throw new InvalidOperationException($"Not allowed: '{untrustedInput.ToString()}'");
}
}
Les nouvelles API sont disponibles à partir du System.Reflection.Metadata package NuGet, qui peut être utilisé avec des versions .NET de bas niveau.
Expressions régulières
[GeneratedRegex] sur les propriétés
.NET 7 a introduit le Regex générateur de source et l’attribut GeneratedRegexAttribute correspondant.
La méthode partielle suivante sera générée à la source avec tout le code nécessaire à l’implémentation de ce Regex.
[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWord();
C# 13 prend en charge les propriétés partielles en plus des méthodes partielles. Par conséquent, à partir de .NET 9, vous pouvez également utiliser [GeneratedRegex(...)] sur une propriété.
La propriété partielle suivante est l’équivalent de la propriété de l’exemple précédent.
[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWordProperty { get; }
Regex.EnumerateSplits
La Regex classe fournit une Split méthode similaire au concept de la String.Split méthode. Avec String.Split, vous fournissez un ou plusieurs char ou string séparateurs, et l'implémentation fractionne le texte d'entrée sur ces séparateurs. Avec Regex.Split, au lieu de spécifier le séparateur en tant que char ou string, il est spécifié comme modèle d’expression régulière.
L’exemple suivant illustre Regex.Split.
foreach (string s in Regex.Split("Hello, world! How are you?", "[aeiou]"))
{
Console.WriteLine($"Split: \"{s}\"");
}
// Output, split by all English vowels:
// Split: "H"
// Split: "ll"
// Split: ", w"
// Split: "rld! H"
// Split: "w "
// Split: "r"
// Split: " y"
// Split: ""
// Split: "?"
Toutefois, Regex.Split n’accepte qu’une string entrée et ne prend pas en charge l’entrée fournie en tant que ReadOnlySpan<char>. En outre, il génère l’ensemble complet de découpages sous la forme d’un string[], ce qui nécessite d'allouer le tableau string afin de contenir les résultats et un string pour chaque découpage. Dans .NET 9, la nouvelle EnumerateSplits méthode permet d’effectuer la même opération, mais avec une entrée basée sur l’étendue et sans entraîner d’allocation pour les résultats. Il accepte une ReadOnlySpan<char> énumération et retourne une énumérable d’objets Range qui représentent les résultats.
L’exemple suivant illustre Regex.EnumerateSplits en prenant ReadOnlySpan<char> comme entrée.
ReadOnlySpan<char> input = "Hello, world! How are you?";
foreach (Range r in Regex.EnumerateSplits(input, "[aeiou]"))
{
Console.WriteLine($"Split: \"{input[r]}\"");
}
Sérialisation (System.Text.Json)
- Options de mise en retrait
- Options web par défaut singleton
- JsonSchemaExporter
- Respecter les annotations pouvant accepter la valeur Null
- Exiger des paramètres de constructeur non facultatifs
- Trier les propriétés JsonObject
- Personnaliser les noms des membres d’énumération
- Diffuser en continu plusieurs documents JSON
Options d’indentation
JsonSerializerOptions inclut de nouvelles propriétés qui vous permettent de personnaliser le caractère de retrait et la taille de mise en retrait du JSON écrit.
var options = new JsonSerializerOptions
{
WriteIndented = true,
IndentCharacter = '\t',
IndentSize = 2,
};
string json = JsonSerializer.Serialize(
new { Value = 1 },
options
);
Console.WriteLine(json);
//{
// "Value": 1
//}
Options web par défaut singleton
Si vous souhaitez sérialiser avec les options par défaut qui ASP.NET Core utilise pour les applications web, utilisez le nouveau JsonSerializerOptions.Web singleton.
string webJson = JsonSerializer.Serialize(
new { SomeValue = 42 },
JsonSerializerOptions.Web // Defaults to camelCase naming policy.
);
Console.WriteLine(webJson);
// {"someValue":42}
JsonSchemaExporter
JSON est fréquemment utilisé pour représenter des types dans les signatures de méthode dans le cadre de schémas d’appel de procédure distante. Il est utilisé, par exemple, dans le cadre de spécifications OpenAPI ou dans le cadre d’un appel d’outils avec des services IA comme ceux d’OpenAI. Les développeurs peuvent sérialiser et désérialiser des types .NET au format JSON à l’aide de System.Text.Json. Mais ils doivent également être en mesure d’obtenir un schéma JSON qui décrit la forme du type .NET (autrement dit, décrit la forme de ce qui serait sérialisé et ce qui peut être désérialisé). System.Text.Json fournit désormais le JsonSchemaExporter type, qui prend en charge la génération d’un schéma JSON qui représente un type .NET.
Pour plus d’informations, consultez l’exportateur de schéma JSON.
Respecter les annotations de nullabilité
System.Text.Json reconnaît désormais les annotations de nullabilité des propriétés et peut être configuré pour les appliquer pendant la sérialisation et la désérialisation à l'aide du flag RespectNullableAnnotations.
Le code suivant montre comment définir l’option :
public static void RunIt()
{
JsonSerializerOptions options = new() { RespectNullableAnnotations = true };
// Throws exception: System.Text.Json.JsonException: The property or field
// 'Title' on type 'Serialization+Book' doesn't allow getting null values.
// Consider updating its nullability annotation.
JsonSerializer.Serialize(new Book { Title = null! }, options);
// Throws exception: System.Text.Json.JsonException: The property or field
// 'Title' on type 'Serialization+Book' doesn't allow setting null values.
// Consider updating its nullability annotation.
JsonSerializer.Deserialize<Book>("""{ "Title" : null }""", options);
}
public class Book
{
public required string Title { get; set; }
public string? Author { get; set; }
public int PublishYear { get; set; }
}
Pour plus d’informations, consultez Respecter les annotations pouvant accepter la valeur Null.
Exiger des paramètres de constructeur non facultatifs
Historiquement, lors de l'utilisation de la désérialisation basée sur le constructeur, System.Text.Json a traité les paramètres de constructeur non facultatifs comme s'ils étaient facultatifs. Vous pouvez modifier ce comportement à l’aide du nouvel RespectRequiredConstructorParameters indicateur.
Le code suivant montre comment définir l’option :
JsonSerializerOptions options = new() { RespectRequiredConstructorParameters = true };
// Throws exception: System.Text.Json.JsonException: JSON deserialization
// for type 'Serialization+MyPoco' was missing required properties including: 'Value'.
JsonSerializer.Deserialize<MyPoco>("""{}""", options);
Le type MyPoco est défini comme suit :
record MyPoco(string Value);
Pour plus d’informations, consultez Paramètres de constructeur non facultatifs.
Organiser les propriétés de JsonObject
Le JsonObject type expose désormais des API de type dictionnaire ordonné qui activent la manipulation explicite de l’ordre des propriétés.
JsonObject jObj = new()
{
["key1"] = true,
["key3"] = 3
};
Console.WriteLine(jObj is IList<KeyValuePair<string, JsonNode?>>); // True.
// Insert a new key-value pair at the correct position.
int key3Pos = jObj.IndexOf("key3") is int i and >= 0 ? i : 0;
jObj.Insert(key3Pos, "key2", "two");
foreach (KeyValuePair<string, JsonNode?> item in jObj)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
// Output:
// key1: true
// key2: two
// key3: 3
Pour plus d’informations, consultez Manipuler l’ordre des propriétés.
Personnaliser les noms des membres d’énumération
Le nouvel System.Text.Json.Serialization.JsonStringEnumMemberNameAttribute attribut peut être utilisé pour personnaliser les noms des membres d’énumération individuels pour les types sérialisés sous forme de chaînes :
JsonSerializer.Serialize(MyEnum.Value1 | MyEnum.Value2); // "Value1, Custom enum value"
[Flags, JsonConverter(typeof(JsonStringEnumConverter))]
enum MyEnum
{
Value1 = 1,
[JsonStringEnumMemberName("Custom enum value")]
Value2 = 2,
}
Pour plus d’informations, consultez Noms personnalisés des membres d’énumération.
Diffuser en continu plusieurs documents JSON
System.Text.Json.Utf8JsonReader prend désormais en charge la lecture de documents JSON séparés par plusieurs espaces blancs à partir d’une seule mémoire tampon ou d’un flux. Par défaut, le lecteur génère une exception s’il détecte des caractères non blancs après le premier document de niveau supérieur. Vous pouvez modifier ce comportement à l’aide de l’indicateur AllowMultipleValues .
Pour plus d’informations, consultez Lire plusieurs documents JSON.
Étendues
Dans le code haute performance, les segments sont souvent utilisés pour éviter d’allouer inutilement des chaînes de caractères. Span<T> et ReadOnlySpan<T> continuent à révolutionner la façon dont le code est écrit dans .NET, et à chaque version, de plus en plus de méthodes sont ajoutées qui fonctionnent sur des spans. .NET 9 inclut les mises à jour suivantes liées à l’étendue :
- Assistants de fichiers
-
Surcharges
params ReadOnlySpan<T> - Itérer sur les segments ReadOnlySpan<char>.Split()
Assistants de fichiers
La classe File dispose désormais de nouveaux assistants pour écrire facilement et directement ReadOnlySpan<char>/ReadOnlySpan<byte> ainsi que ReadOnlyMemory<char>/ReadOnlyMemory<byte> dans des fichiers.
Le code suivant écrit efficacement un ReadOnlySpan<char> dans un fichier.
ReadOnlySpan<char> text = ...;
File.WriteAllText(filePath, text);
De nouvelles méthodes d'extension StartsWith<T>(ReadOnlySpan<T>, T) et EndsWith<T>(ReadOnlySpan<T>, T) ont également été ajoutées pour les segments, permettant de tester facilement si un ReadOnlySpan<T> commence ou se termine par une valeur spécifique T.
Le code suivant utilise ces nouvelles API pratiques.
ReadOnlySpan<char> text = "some arbitrary text";
return text.StartsWith('"') && text.EndsWith('"'); // false
Surcharges params ReadOnlySpan<T>
C# a toujours pris en charge le marquage des paramètres de tableau comme params. Ce mot clé active une syntaxe d’appel simplifiée. Par exemple, le deuxième paramètre de la String.Join(String, String[]) méthode est marqué avec params. Vous pouvez appeler cette surcharge avec un tableau ou en transmettant les valeurs individuellement :
string result = string.Join(", ", new string[3] { "a", "b", "c" });
string result = string.Join(", ", "a", "b", "c");
Avant .NET 9, lorsque vous transmettez les valeurs individuellement, le compilateur C# émet du code identique au premier appel en produisant un tableau implicite autour des trois arguments.
À compter de C# 13, vous pouvez utiliser params avec n’importe quel argument qui peut être construit via une expression de collection, y compris les étendues (Span<T> et ReadOnlySpan<T>). C’est bénéfique pour l’utilisation et les performances. Le compilateur C# peut stocker les arguments sur la pile, les envelopper dans une plage et les transmettre à la méthode, ce qui évite l'allocation implicite de tableau qui aurait été nécessaire autrement.
.NET 9 inclut plus de 60 méthodes avec un params ReadOnlySpan<T> paramètre. Certaines sont de nouvelles surcharges, et d’autres sont des méthodes existantes qui ont déjà utilisé ReadOnlySpan<T> mais dont ce paramètre est désormais marqué avec params. L’effet net est que si vous effectuez une mise à niveau vers .NET 9 et recompilez votre code, vous verrez des améliorations des performances sans apporter de modifications de code. Cela est dû au fait que le compilateur préfère établir une liaison à des surcharges basées sur des étendues plutôt qu’aux surcharges basées sur des tableaux.
Par exemple, String.Join inclut maintenant la surcharge suivante, qui implémente le nouveau modèle : String.Join(String, ReadOnlySpan<String>)
À présent, un appel comme string.Join(", ", "a", "b", "c") est effectué sans allouer un tableau pour passer les arguments "a", "b", et "c".
Itérer sur les segments ReadOnlySpan<char>.Split()
string.Split est une méthode pratique pour partitionner rapidement une chaîne avec un ou plusieurs séparateurs fournis. Pour le code axé sur les performances, toutefois, le profil d’allocation de string.Split peut être prohibitif, car il alloue une chaîne pour chaque composant analysé et utilise un string[] pour les stocker tous. Elle ne fonctionne pas non plus avec des segments. Par conséquent, si vous avez un ReadOnlySpan<char>, vous êtes obligé d’allouer une autre chaîne lorsque vous la convertissez en chaîne pour pouvoir appeler string.Split dessus.
Dans .NET 8, un ensemble de méthodes Split et SplitAny a été introduit pour ReadOnlySpan<char>. Au lieu de retourner un nouveau string[], ces méthodes acceptent une destination Span<Range> dans laquelle les index englobants pour chaque composant sont écrits. Cela rend l’opération entièrement sans allocation. Ces méthodes sont appropriées pour être utilisées lorsque le nombre de plages est à la fois connu et petit.
Dans .NET 9, de nouvelles surcharges de Split et SplitAny ont été ajoutées pour permettre l'analyse incrémentielle d'un ReadOnlySpan<T> avec un nombre a priori inconnu de segments. Les nouvelles méthodes permettent d’énumérer chaque segment, qui est également représenté comme Range, élément qui peut être utilisé pour découper l’étendue d’origine.
public static bool ListContainsItem(ReadOnlySpan<char> span, string item)
{
foreach (Range segment in span.Split(','))
{
if (span[segment].SequenceEquals(item))
{
return true;
}
}
return false;
}
System.Formats
La position ou le décalage des données d'un objet TarEntry dans le flux englobant est désormais une propriété publique. TarEntry.DataOffset retourne la position dans le flux d’archivage de l’entrée où se trouve le premier octet de données de l’entrée. Les données de l’entrée sont encapsulées dans un sous-flux auquel vous pouvez accéder via TarEntry.DataStream, ce qui masque la position réelle des données par rapport au flux d’archivage. Cela suffit pour la plupart des utilisateurs, mais si vous avez besoin d’une plus grande flexibilité et que vous souhaitez connaître la position de départ réelle des données dans le flux d’archivage, la nouvelle TarEntry.DataOffset API facilite la prise en charge des fonctionnalités telles que l’accès simultané avec des fichiers TAR très volumineux.
// Create stream for tar ball data in Azure Blob Storage.
BlobClient blobClient = new(connectionString, blobContainerName, blobName);
Stream blobClientStream = await blobClient.OpenReadAsync(options, cancellationToken);
// Create TarReader for the stream and get a TarEntry.
TarReader tarReader = new(blobClientStream);
System.Formats.Tar.TarEntry? tarEntry = await tarReader.GetNextEntryAsync();
if (tarEntry is null)
return;
// Get position of TarEntry data in blob stream.
long entryOffsetInBlobStream = tarEntry.DataOffset;
long entryLength = tarEntry.Length;
// Create a separate stream.
Stream newBlobClientStream = await blobClient.OpenReadAsync(options, cancellationToken);
newBlobClientStream.Seek(entryOffsetInBlobStream, SeekOrigin.Begin);
// Read tar ball content from separate BlobClient stream.
byte[] bytes = new byte[entryLength];
await newBlobClientStream.ReadExactlyAsync(bytes, 0, (int)entryLength);
System.Guid
NewGuid() crée un Guid rempli principalement de données aléatoires sécurisées par chiffrement, en suivant la spécification UUID Version 4 dans RFC 9562. Cette même RFC définit également d'autres versions, y compris la version 7, qui est caractérisée par un champ de valeur ordonnée par le temps, basée sur la source d'horodatage Unix Epoch, largement implémentée et bien connue. En d’autres termes, la plupart des données sont toujours aléatoires, mais certaines d’entre elles sont réservées aux données basées sur un horodatage, ce qui permet à ces valeurs d’avoir un ordre de tri naturel. Dans .NET 9, vous pouvez créer une Guid selon la version 7 grâce aux nouvelles méthodes Guid.CreateVersion7() et Guid.CreateVersion7(DateTimeOffset). Vous pouvez également utiliser la nouvelle Version propriété pour récupérer le champ de version d’un Guid objet.
System.IO
- Compression avec zlib-ng
- Options de compression ZLib et Brotli
- Documents XPS à partir d’une imprimante virtuelle XPS
Compression avec zlib-ng
System.IO.Compression les fonctionnalités telles que ZipArchive, DeflateStream, GZipStreamet ZLibStream sont toutes basées principalement sur la bibliothèque zlib. À partir de .NET 9, ces fonctionnalités utilisent plutôt zlib-ng, une bibliothèque qui génère un traitement plus cohérent et efficace sur un plus grand éventail de systèmes d’exploitation et de matériel.
Options de compression ZLib et Brotli
ZLibCompressionOptionset BrotliCompressionOptions sont de nouveaux types pour définir le niveau de compression et la stratégie spécifiques à l’algorithme (Default, , Filtered, HuffmanOnlyRunLengthEncodingou Fixed). Ces types sont destinés aux utilisateurs qui souhaitent des paramètres plus affinés que la seule option existante, <System.IO.Compression.CompressionLevel>.
Les nouveaux types d’options de compression peuvent être développés à l’avenir.
L’extrait de code suivant montre un exemple d’utilisation :
private MemoryStream CompressStream(Stream uncompressedStream)
{
MemoryStream compressorOutput = new();
using ZLibStream compressionStream = new(
compressorOutput,
new ZLibCompressionOptions()
{
CompressionLevel = 6,
CompressionStrategy = ZLibCompressionStrategy.HuffmanOnly
}
);
uncompressedStream.CopyTo(compressionStream);
compressionStream.Flush();
return compressorOutput;
}
Documents XPS à partir d’une imprimante virtuelle XPS
Les documents XPS provenant d’une imprimante virtuelle V4 XPS n’ont pas pu être ouverts précédemment à l’aide de la System.IO.Packaging bibliothèque, en raison d’un manque de prise en charge de la gestion des fichiers .piece . Cet écart a été résolu dans .NET 9.
System.Numerics
- Limite supérieure de BigInteger
-
BigMulApis - API de conversion de vecteurs
- API de création de vecteurs
- Accélération supplémentaire
Limite supérieure de BigInteger
BigInteger prend en charge la représentation de valeurs entières d’une longueur essentiellement arbitraire. Toutefois, dans la pratique, la longueur est limitée par des limites de l’ordinateur sous-jacent, telles que la mémoire disponible ou le temps nécessaire pour calculer une expression donnée. En outre, il existe certaines API qui échouent en fonction des entrées qui entraînent une valeur trop importante. En raison de ces limites, .NET 9 applique une longueur maximale , BigIntegerc’est-à-dire qu’il ne peut contenir pas plus de (2^31) - 1 (environ 2,14 milliards) bits. Ce nombre représente une allocation de près de 256 Mo et contient environ 646,5 millions de chiffres. Cette nouvelle limite garantit que toutes les API exposées aient un bon comportement et restent cohérentes, tout en permettant des valeurs nettement au-delà de la plupart des scénarios d’utilisation.
API BigMul
BigMul est une opération qui produit le produit complet de deux nombres. .NET 9 ajoute des API dédiées BigMul sur int, longet uintulong dont le type de retour est le type entier supérieur suivant à celui des types de paramètres.
Les nouvelles API sont les suivantes :
-
BigMul(Int32, Int32) (renvoie
long) -
BigMul(Int64, Int64) (renvoie
Int128) -
BigMul(UInt32, UInt32) (renvoie
ulong) -
BigMul(UInt64, UInt64) (renvoie
UInt128)
API de conversion de vecteurs
.NET 9 ajoute des API d’extension dédiées pour la conversion entre Vector2, , Vector3Vector4, , Quaternionet Plane.
Les nouvelles API sont les suivantes :
- AsPlane(Vector4)
- AsQuaternion(Vector4)
- AsVector2(Vector4)
- AsVector3(Vector4)
- AsVector4(Plane)
- AsVector4(Quaternion)
- AsVector4(Vector2)
- AsVector4(Vector3)
- AsVector4Unsafe(Vector2)
- AsVector4Unsafe(Vector3)
Pour les conversions de même taille, telles que entre Vector4, Quaternionet Plane, ces conversions sont des coûts nuls. Cela vaut également pour les conversions restrictives, comme de Vector4 à Vector2 ou Vector3. Pour les conversions de type élargies, telles que depuis Vector2 ou Vector3 vers Vector4, il existe l’API normale, qui initialise de nouveaux éléments à 0, et une API Unsafe suffixée qui laisse ces nouveaux éléments non définis, ce qui peut donc ne pas entraîner de coût.
API de création de vecteurs
Il existe de nouvelles API Create exposées pour Vector, Vector2, Vector3 et Vector4 qui établissent la parité avec les API équivalentes exposées pour les types de vecteurs matériels dans l’espace de noms System.Runtime.Intrinsics.
Pour plus d’informations sur les nouvelles API, consultez :
Ces API sont principalement destinées à des raisons pratiques et pour une cohérence globale des types accélérés par SIMD de .NET.
Accélération supplémentaire
Des améliorations supplémentaires des performances ont été apportées à de nombreux types dans l’espace de noms System.Numerics, notamment à BigInteger, Vector2, Vector3, Vector4, Quaternion et Plane.
Dans certains cas, cela a entraîné une augmentation de vitesse de 2 à 5x dans les API principales, notamment Matrix4x4 la multiplication, la création de Plane à partir d'une série de sommets, Quaternion la concaténation et le calcul du produit croisé d’un Vector3.
Il existe également une prise en charge de l’assemblage de constantes pour l’API SinCos, qui calcule à la fois Sin(x) et Cos(x) dans un seul appel, ce qui la rend plus efficace.
Tenseurs pour l’IA
Les tenseurs sont la structure de données de pierre angulaire de l’intelligence artificielle (IA). Elles peuvent souvent être considérées comme des tableaux multidimensionnels.
Les tenseurs sont utilisés pour :
- Représentez et encodez des données telles que des séquences de texte (jetons), des images, des vidéos et de l’audio.
- Manipuler efficacement des données à dimension supérieure.
- Appliquez efficacement des calculs sur des données à dimension supérieure.
- Stockez les informations de poids et les calculs intermédiaires (dans les réseaux neuronaux).
Pour utiliser les API tensor .NET, installez le package NuGet System.Numerics.Tensors .
Nouveau type Tensor<T>
Le nouveau Tensor<T> type étend les fonctionnalités IA des bibliothèques et du runtime .NET. Ce type :
- Fournit une interopérabilité efficace avec des bibliothèques IA telles que ML.NET, TorchSharp et ONNX Runtime en utilisant zéro copie lorsque c'est possible.
- S’appuie sur TensorPrimitives pour des opérations mathématiques efficaces.
- Permet une manipulation facile et efficace des données en fournissant des opérations d’indexation et de découpage.
- N’est pas un remplacement pour les bibliothèques IA et Machine Learning existantes. Au lieu de cela, il est destiné à fournir un ensemble commun d’API pour réduire la duplication du code et les dépendances, et pour obtenir de meilleures performances à l’aide des dernières fonctionnalités d’exécution.
Les codes suivants illustrent certaines API incluses avec le nouveau Tensor<T> type.
// Create a tensor (1 x 3).
Tensor<int> t0 = Tensor.Create([1, 2, 3], [1, 3]); // [[1, 2, 3]]
// Reshape tensor (3 x 1).
Tensor<int> t1 = t0.Reshape(3, 1); // [[1], [2], [3]]
// Slice tensor (2 x 1).
Tensor<int> t2 = t1.Slice(1.., ..); // [[2], [3]]
// Broadcast tensor (3 x 1) -> (3 x 3).
// [
// [ 1, 1, 1],
// [ 2, 2, 2],
// [ 3, 3, 3]
// ]
var t3 = Tensor.Broadcast<int>(t1, [3, 3]);
// Math operations.
var t4 = Tensor.Add(t0, 1); // [[2, 3, 4]]
var t5 = Tensor.Add(t0.AsReadOnlyTensorSpan(), t0); // [[2, 4, 6]]
var t6 = Tensor.Subtract(t0, 1); // [[0, 1, 2]]
var t7 = Tensor.Subtract(t0.AsReadOnlyTensorSpan(), t0); // [[0, 0, 0]]
var t8 = Tensor.Multiply(t0, 2); // [[2, 4, 6]]
var t9 = Tensor.Multiply(t0.AsReadOnlyTensorSpan(), t0); // [[1, 4, 9]]
var t10 = Tensor.Divide(t0, 2); // [[0.5, 1, 1.5]]
var t11 = Tensor.Divide(t0.AsReadOnlyTensorSpan(), t0); // [[1, 1, 1]]
Remarque
Cette API est marquée comme expérimentale pour .NET 9.
TensorPrimitives
La System.Numerics.Tensors bibliothèque inclut la TensorPrimitives classe, qui fournit des méthodes statiques pour effectuer des opérations numériques sur des étendues de valeurs. Dans .NET 9, l’étendue des méthodes exposées par TensorPrimitives a été considérablement étendue, passant de 40 (en .NET 8) à près de 200 surcharges. La surface englobe des opérations numériques familières de types tels que Math et MathF. Il inclut également les interfaces mathématiques génériques comme INumber<TSelf>, sauf au lieu de traiter une valeur individuelle, ils traitent une étendue de valeurs. De nombreuses opérations ont également été accélérées via des implémentations optimisées par SIMD pour .NET 9.
TensorPrimitives expose désormais des surcharges génériques pour n’importe quel type T qui implémente une certaine interface. (La version .NET 8 incluait uniquement des surcharges pour manipuler des étendues de valeurs de float.) Par exemple, la nouvelle surcharge CosineSimilarity<T>(ReadOnlySpan<T>, ReadOnlySpan<T>) effectue une similarité cosinus sur deux vecteurs de valeurs de float, double, ou Half, ou de valeurs de tout autre type qui implémente IRootFunctions<TSelf>.
Comparez la précision de l’opération de similarité cosinus sur deux vecteurs de type float par rapport doubleà :
ReadOnlySpan<float> vector1 = [1, 2, 3];
ReadOnlySpan<float> vector2 = [4, 5, 6];
Console.WriteLine(TensorPrimitives.CosineSimilarity(vector1, vector2));
// Prints 0.9746318
ReadOnlySpan<double> vector3 = [1, 2, 3];
ReadOnlySpan<double> vector4 = [4, 5, 6];
Console.WriteLine(TensorPrimitives.CosineSimilarity(vector3, vector4));
// Prints 0.9746318461970762
Thread
Les API de threading incluent des améliorations pour l’itération à travers les tâches, pour les canaux hiérarchisés, qui peuvent classer leurs éléments plutôt que de suivre le principe premier entré, premier sorti (FIFO), et Interlocked.CompareExchange pour plus de types.
Task.WhenEach
Une variété d’API utiles ont été ajoutées pour l’utilisation d’objets Task<TResult> . La nouvelle méthode Task.WhenEach vous permet d’effectuer une itération dans les tâches à mesure qu’elles se terminent à l’aide d’une instruction await foreach. Vous n’avez plus besoin d’effectuer des opérations telles qu’appeler Task.WaitAny à plusieurs reprises sur un ensemble de tâches pour choisir celle qui se terminera juste après.
Le code suivant effectue plusieurs HttpClient appels et traite leurs résultats à mesure qu'ils sont exécutés.
using HttpClient http = new();
Task<string> dotnet = http.GetStringAsync("http://dot.net");
Task<string> bing = http.GetStringAsync("http://www.bing.com");
Task<string> ms = http.GetStringAsync("http://microsoft.com");
await foreach (Task<string> t in Task.WhenEach(bing, dotnet, ms))
{
Console.WriteLine(t.Result);
}
Canal non lié hiérarchisé par ordre de priorité
L'espace de noms System.Threading.Channels vous permet de créer des canaux FIFO (premier entré, premier sorti) à l'aide des méthodes CreateBounded et CreateUnbounded. Avec les canaux FIFO, les éléments sont lus à partir du canal dans l’ordre dans lequel ils ont été écrits. Dans .NET 9, une nouvelle méthode CreateUnboundedPrioritized a été ajoutée, qui ordonne les éléments de sorte que l’élément suivant lu à partir du canal soit celui considéré comme le plus important, selon Comparer<T>.Default ou un critère personnalisé IComparer<T>.
L’exemple suivant utilise la nouvelle méthode pour créer un canal qui génère les nombres 1 à 5 dans l’ordre, même s’ils sont écrits dans le canal dans un ordre différent.
Channel<int> c = Channel.CreateUnboundedPrioritized<int>();
await c.Writer.WriteAsync(1);
await c.Writer.WriteAsync(5);
await c.Writer.WriteAsync(2);
await c.Writer.WriteAsync(4);
await c.Writer.WriteAsync(3);
c.Writer.Complete();
while (await c.Reader.WaitToReadAsync())
{
while (c.Reader.TryRead(out int item))
{
Console.Write($"{item} ");
}
}
// Output: 1 2 3 4 5
Interlocked.CompareExchange pour plus de types
Dans les versions précédentes de .NET, Interlocked.Exchange et Interlocked.CompareExchange avaient des surcharges pour travailler avec int, uint, long, ulong, nint, nuint, float, double et object, ainsi qu’une surcharge générique pour l’utilisation de n’importe quel type de référence T. Dans .NET 9, il existe de nouvelles surcharges pour travailler de manière atomique avec byte, sbyte, short et ushort. En outre, la contrainte générique sur les surcharges génériques Interlocked.Exchange<T> et Interlocked.CompareExchange<T> a été supprimée, ce qui signifie que ces méthodes ne sont plus contraintes de fonctionner uniquement avec des types de référence. Ils peuvent désormais travailler avec n’importe quel type primitif, qui inclut tous les types mentionnés ainsi que bool , charainsi que enum tout type.