Nouveautés du runtime .NET 8
Cet article décrit les nouvelles fonctionnalités du runtime .NET pour .NET 8.
Optimisation des performances
.NET 8 inclut des améliorations apportées à la génération de code et à la compilation juste-à-temps (JIT) :
- Améliorations des performances d’Arm64
- Améliorations SIMD
- Prise en charge des extensions AVX-512 ISA (voir Vector512 et AVX-512)
- Améliorations du natif Cloud
- Améliorations du débit JIT
- Optimisations générales et des boucles
- Accès optimisé pour les champs marqués avec ThreadStaticAttribute
- Allocation de registre consécutive. Arm64 propose deux instructions pour la recherche de vecteurs de table, qui exigent que toutes les entités de leurs opérandes tuples soient présentes dans des registres consécutifs.
- JIT/NativeAOT peut désormais dérouler et vectoriser automatiquement certaines opérations de mémoire avec SIMD, telles que la comparaison, la copie et la mise à zéro, s’il peut déterminer leurs tailles au moment de la compilation.
Par ailleurs, l’optimisation dynamique guidée par profil (PGO) a été améliorée et est désormais activée par défaut. Vous n’avez plus besoin d’utiliser une option de configuration d’exécution pour l’activer. L’optimisation guidée par profil dynamique fonctionne main dans la main avec la compilation hiérarchisée pour optimiser davantage le code en fonction de l’instrumentation supplémentaire mise en place pendant le niveau 0.
En moyenne, la PGO dynamique augmente les performances d’environ 15 %. Dans une série d’environ 4 600 tests, 23 % des tests ont vu des améliorations de performances de l’ordre de 20 % ou plus.
Promotion du struct Codegen
.NET 8 inclut un nouveau passe d’optimisation de la promotion physique pour codegen qui généralise la capacité du JIT à promouvoir les variables de struct. Cette optimisation (également appelée remplacement scalaire des agrégats) remplace les champs de variables de struct par des variables primitives que le JIT peut ensuite inclure dans son raisonnement et optimiser plus précisément.
Le JIT prenait déjà en charge cette optimisation, mais avec plusieurs limitations importantes, notamment :
- Elle n’était prise en charge que pour les structs avec quatre champs ou moins.
- Elle n’était prise en charge que si chaque champ était un type primitif ou un struct simple encapsulant un type primitif.
La promotion physique supprime ces limitations, ce qui résout un certain nombre de problèmes JIT historiques.
Nettoyage de la mémoire
.NET 8 ajoute une fonctionnalité permettant d’ajuster la limite de mémoire à la volée. Cela est utile dans les scénarios de service cloud, où la demande vient et va. Pour être rentables, les services doivent être mis à l’échelle en fonction de la consommation des ressources à mesure que la demande fluctue. Lorsqu’un service détecte une diminution de la demande, il peut réduire la consommation de ressources en réduisant sa limite de mémoire. Auparavant, cela échouait, car le récupérateur de mémoire (GC) ignorait la modification et pouvait allouer plus de mémoire que la nouvelle limite. Avec cette modification, vous pouvez appeler l’API RefreshMemoryLimit() pour mettre à jour le GC avec la nouvelle limite de mémoire.
Il existe certaines limites à connaître :
- Sur les plateformes 32 bits (par exemple, Windows x86 et Linux ARM), .NET n’est pas en mesure d’établir une nouvelle limite matérielle de tas s’il n’en existe pas déjà une.
- L’API peut retourner un code d’état différent de zéro indiquant l’échec de l’actualisation. Cela peut se produire si le scale-down est trop agressif et ne laisse aucune marge de manœuvre au GC. Dans ce cas, envisagez d’appeler
GC.Collect(2, GCCollectionMode.Aggressive)
pour réduire l’utilisation actuelle de la mémoire, puis réessayez. - Si vous augmentez la limite de mémoire au-delà de la taille que le GC croit que le processus peut gérer au démarrage, l’appel
RefreshMemoryLimit
réussit, mais il ne pourra pas utiliser plus de mémoire que ce qu’il perçoit comme limite.
L’extrait de code suivant montre comment nommer l’API.
GC.RefreshMemoryLimit();
Vous pouvez également actualiser certains des paramètres de configuration GC liés à la limite de mémoire. L’extrait de code suivant définit la limite matérielle du tas à 100 miooctets (Mio) :
AppContext.SetData("GCHeapHardLimit", (ulong)100 * 1_024 * 1_024);
GC.RefreshMemoryLimit();
L’API peut lever une InvalidOperationException valeur si la limite matérielle n’est pas valide, par exemple, dans le cas de pourcentages de limite de tas négatifs et si la limite matérielle est trop faible. Cela peut se produire, si la limite matérielle du tas définie par l’actualisation, due à de nouveaux paramètres AppData ou engendrée par les modifications de la limite de mémoire du conteneur, est inférieure à ce qui est déjà validé.
Globalisation pour les applications mobiles
Les applications mobiles sur iOS, tvOS et MacCatalyst peuvent choisir un nouveau mode de globalisation hybride qui utilise un pack ICU plus léger. En mode hybride, les données de globalisation sont partiellement extraites du pack ICU et partiellement des appels dans l’API native. Le mode hybride sert tous les paramètres régionaux pris en charge par les mobiles.
Le mode hybride convient mieux aux applications qui ne peuvent pas fonctionner en mode de globalisation invariant et qui utilisent des cultures supprimées des données d’ICU sur les mobiles. Vous pouvez également l’utiliser lorsque vous souhaitez charger un fichier de données ICU plus petit. (Le fichier icudt_hybrid.dat est plus petit de 34,5 % que le fichier de données d’ICU par défaut icudt.dat.)
Pour utiliser le mode de globalisation hybride, définissez la propriété MSBuild HybridGlobalization
sur true :
<PropertyGroup>
<HybridGlobalization>true</HybridGlobalization>
</PropertyGroup>
Il existe certaines limites à connaître, comme les suivantes :
- En raison des limitations de l’API native, toutes les API de globalisation ne sont pas prises en charge en mode hybride.
- Certaines API prises en charge ont un comportement différent.
Pour vérifier si votre application est affectée, consultez Différences de comportement.
COM Interop générée par la source
.NET 8 inclut un nouveau générateur source qui prend en charge l’interopérabilité avec les interfaces COM. Vous pouvez utiliser GeneratedComInterfaceAttribute pour marquer une interface en tant qu’interface COM pour le générateur source. Le générateur source génère ensuite du code pour activer l’appel du code C# vers du code non managé. Il génère également du code pour activer l’appel de code non managé en C#. Ce générateur source s’intègre à LibraryImportAttribute, et vous pouvez utiliser des types avec GeneratedComInterfaceAttribute comme paramètres et retourner des types dans les méthodes avec les attributs de LibraryImport
.
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
[GeneratedComInterface]
[Guid("5401c312-ab23-4dd3-aa40-3cb4b3a4683e")]
partial interface IComInterface
{
void DoWork();
}
internal partial class MyNativeLib
{
[LibraryImport(nameof(MyNativeLib))]
public static partial void GetComInterface(out IComInterface comInterface);
}
Le générateur source prend également en charge le nouvel attribut GeneratedComClassAttribute pour vous permettre de passer des types qui implémentent des interfaces avec l’attribut GeneratedComInterfaceAttribute à du code non managé. Le générateur source génère le code nécessaire pour exposer un objet COM qui implémente les interfaces et transfère les appels à l’implémentation managée.
Les méthodes sur les interfaces avec l’attribut GeneratedComInterfaceAttribute prennent en charge tous les mêmes types que LibraryImportAttribute
, et LibraryImportAttribute
prend désormais en charge les types avec les attributs GeneratedComInterface
et GeneratedComClass
.
Si votre code C# utilise uniquement une interface avec les attributs GeneratedComInterface
pour encapsuler un objet COM à partir de code non managé ou encapsuler un objet managé à partir de C# pour l’exposer à du code non managé, vous pouvez utiliser les options de la propriété Options pour personnaliser le code qui sera généré. Ces options signifient que vous n’avez pas besoin d’écrire des marshallers pour les scénarios que vous savez ne seront pas utilisés.
Le générateur source utilise le nouveau type StrategyBasedComWrappers pour créer et gérer les wrappers d’objets COM et les wrappers d’objets managés. Ce nouveau type gère l’expérience utilisateur .NET attendue pour COM Interop, tout en fournissant des points de personnalisation pour les utilisateurs avancés. Si votre application a son propre mécanisme pour définir des types à partir de COM ou si vous devez prendre en charge des scénarios que le COM généré par la source ne prend actuellement pas en charge, envisagez d’utiliser le nouveau type StrategyBasedComWrappers pour ajouter les fonctionnalités manquantes pour votre scénario et obtenir la même expérience utilisateur .NET pour vos types COM.
Si vous utilisez Visual Studio, les nouveaux analyseurs et correctifs de code facilitent la conversion de votre code COM Interop existant pour utiliser l’interopérabilité générée par la source. À côté de chaque interface qui a ComImportAttribute, une ampoule offre la possibilité de conversion en interopérabilité générée par la source. Le correctif modifie l’interface pour utiliser l’attribut GeneratedComInterfaceAttribute. À côté de chaque classe qui implémente une interface avec GeneratedComInterfaceAttribute
, une ampoule offre une option permettant d’ajouter l’attribut GeneratedComClassAttribute au type. Une fois vos types convertis, vous pouvez déplacer vos méthodes DllImport
pour utiliser LibraryImportAttribute
.
Limites
Le générateur de source COM ne prend pas en charge l’affinité d’appartement, en utilisant le mot clé new
pour activer une coclasse COM et les API suivantes :
- Interfaces basées sur IDispatch.
- Interfaces basées sur IInspectable.
- Propriétés et événements COM.
Générateur de source de liaison de configuration
.NET 8 introduit un générateur de source pour fournir une configuration AOT compatible avec la découpe dans ASP.NET Core. Le générateur est une alternative à l’implémentation basée sur la réflexion préexistante.
Le générateur source sonde les appels Configure(TOptions), Bind et Get pour récupérer les informations de type à partir de ces derniers. Lorsque le générateur est activé dans un projet, le compilateur choisit implicitement les méthodes générées sur les implémentations d’infrastructure basées sur la réflexion préexistantes.
Aucune modification du code source n’est nécessaire pour utiliser le générateur. Il est activé par défaut dans les applications web AOT. Pour les autres types de projets, le générateur source est désactivé par défaut, mais vous pouvez vous inscrire en définissant la propriété EnableConfigurationBindingGenerator
sur true
dans votre fichier projet :
<PropertyGroup>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
Le code suivant montre un exemple d’appel du binder.
public class ConfigBindingSG
{
static void RunIt(params string[] args)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
IConfigurationSection section = builder.Configuration.GetSection("MyOptions");
// !! Configure call - to be replaced with source-gen'd implementation
builder.Services.Configure<MyOptions>(section);
// !! Get call - to be replaced with source-gen'd implementation
MyOptions? options0 = section.Get<MyOptions>();
// !! Bind call - to be replaced with source-gen'd implementation
MyOptions options1 = new();
section.Bind(options1);
WebApplication app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
}
public class MyOptions
{
public int A { get; set; }
public string S { get; set; }
public byte[] Data { get; set; }
public Dictionary<string, string> Values { get; set; }
public List<MyClass> Values2 { get; set; }
}
public class MyClass
{
public int SomethingElse { get; set; }
}
}
Bibliothèques .NET Core
Cette section comprend les sous-rubriques suivantes :
- Réflexion
- Sérialisation
- Abstraction temporelle
- Améliorations apportées à UTF8
- Méthodes pour l’utilisation du caractère aléatoire
- Types axés sur les performances
- System.Numerics et System.Runtime.Intrinsics
- Validation des données
- Métriques
- Chiffrement
- Mise en réseau
- Méthodes ZipFile basées sur le flux
Réflexion
Les pointeurs de fonction ont été introduits dans .NET 5, mais la prise en charge correspondante de la réflexion n’a pas été ajoutée à ce moment-là. Quand vous utilisiez typeof
ou la réflexion sur un pointeur de fonction, par exemple typeof(delegate*<void>())
ou FieldInfo.FieldType
respectivement, un IntPtr était retourné. À partir de .NET 8, un objet System.Type est retourné à la place. Ce type permet d’accéder aux métadonnées du pointeur de fonction, notamment les conventions d’appel, le type de retour et les paramètres.
Notes
Une instance de pointeur de fonction, qui est une adresse physique à une fonction, continue d’être représentée sous la forme d’un IntPtr. Seul le type de réflexion a changé.
Pour le moment, la nouvelle fonctionnalité est uniquement implémentée dans le runtime CoreCLR et MetadataLoadContext.
De nouvelles API ont été ajoutées à System.Type, telles que IsFunctionPointer, et à System.Reflection.PropertyInfo, System.Reflection.FieldInfo et System.Reflection.ParameterInfo. Le code suivant montre comment utiliser quelques-unes des nouvelles API pour la réflexion.
using System;
using System.Reflection;
// Sample class that contains a function pointer field.
public unsafe class UClass
{
public delegate* unmanaged[Cdecl, SuppressGCTransition]<in int, void> _fp;
}
internal class FunctionPointerReflection
{
public static void RunIt()
{
FieldInfo? fieldInfo = typeof(UClass).GetField(nameof(UClass._fp));
// Obtain the function pointer type from a field.
Type? fpType = fieldInfo?.FieldType;
// New methods to determine if a type is a function pointer.
Console.WriteLine(
$"IsFunctionPointer: {fpType?.IsFunctionPointer}");
Console.WriteLine(
$"IsUnmanagedFunctionPointer: {fpType?.IsUnmanagedFunctionPointer}");
// New methods to obtain the return and parameter types.
Console.WriteLine($"Return type: {fpType?.GetFunctionPointerReturnType()}");
if (fpType is not null)
{
foreach (Type parameterType in fpType.GetFunctionPointerParameterTypes())
{
Console.WriteLine($"Parameter type: {parameterType}");
}
}
// Access to custom modifiers and calling conventions requires a "modified type".
Type? modifiedType = fieldInfo?.GetModifiedFieldType();
// A modified type forwards most members to its underlying type.
Type? normalType = modifiedType?.UnderlyingSystemType;
if (modifiedType is not null)
{
// New method to obtain the calling conventions.
foreach (Type callConv in modifiedType.GetFunctionPointerCallingConventions())
{
Console.WriteLine($"Calling convention: {callConv}");
}
}
// New method to obtain the custom modifiers.
Type[]? modifiers =
modifiedType?.GetFunctionPointerParameterTypes()[0].GetRequiredCustomModifiers();
if (modifiers is not null)
{
foreach (Type modreq in modifiers)
{
Console.WriteLine($"Required modifier for first parameter: {modreq}");
}
}
}
}
L’exemple précédent génère la sortie suivante :
IsFunctionPointer: True
IsUnmanagedFunctionPointer: True
Return type: System.Void
Parameter type: System.Int32&
Calling convention: System.Runtime.CompilerServices.CallConvSuppressGCTransition
Calling convention: System.Runtime.CompilerServices.CallConvCdecl
Required modifier for first parameter: System.Runtime.InteropServices.InAttribute
Sérialisation
Diverses améliorations ont été apportées à la fonctionnalité de sérialisation et de désérialisation System.Text.Json dans .NET 8. Par exemple, vous pouvez personnaliser la gestion des membres qui ne figurent pas dans la charge utile JSON.
Les sections suivantes décrivent d’autres améliorations de sérialisation :
- Prise en charge intégrée de types supplémentaires
- Générateur de source
- Hiérarchies d’interface
- Stratégies d’affectation de noms
- Propriétés en lecture seule
- Désactiver la valeur par défaut basée sur la réflexion
- Nouvelles méthodes d’API JsonNode
- Membres non publics
- API de désérialisation de diffusion en continu
- Méthode d’extension WithAddedModifier
- Nouvelles surcharges JsonContent.Create
- Geler des instances JsonSerializerOptions
Pour plus d’informations sur la sérialisation JSON en général, consultez Sérialisation et désérialisation JSON dans .NET.
Prise en charge intégrée de types supplémentaires
Le sérialiseur prend en charge les types supplémentaires suivants.
Types numériques Half, Int128 et UInt128.
Console.WriteLine(JsonSerializer.Serialize( [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ] )); // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
Valeurs Memory<T> et ReadOnlyMemory<T>.
byte
valeurs sont sérialisées en chaînes Base64 et d’autres types dans des tableaux JSON.JsonSerializer.Serialize<ReadOnlyMemory<byte>>(new byte[] { 1, 2, 3 }); // "AQID" JsonSerializer.Serialize<Memory<int>>(new int[] { 1, 2, 3 }); // [1,2,3]
Générateur de source
.NET 8 inclut des améliorations du générateur source System.Text.Json qui visent à rendre l’expérience AOT native à l’égal du sérialiseur basé sur la réflexion. Par exemple :
Le générateur de source prend désormais en charge la sérialisation des types avec les propriétés
required
etinit
. Ces deux éléments étaient déjà pris en charge dans la sérialisation basée sur la réflexion.Amélioration de la mise en forme du code généré par la source.
JsonSourceGenerationOptionsAttribute parité des fonctionnalités avec JsonSerializerOptions. Pour plus d’informations, consultez Spécifier les options (génération de source).
Diagnostics supplémentaires (tels que SYSLIB1034 et SYSLIB1039).
N’incluez pas les types de propriétés ignorées ou inaccessibles.
Prend en charge l’imbrication de déclarations
JsonSerializerContext
dans des types arbitraires.Prend en charge des types générés par le compilateur ou indicibles dans les scénarios de génération de sources faiblement typées. Étant donné que les types générés par le compilateur ne peuvent pas être spécifiés explicitement par le générateur source, System.Text.Json effectue désormais la résolution ancêtre la plus proche au moment de l’exécution. Cette résolution détermine le supertype le plus approprié avec lequel sérialiser la valeur.
Nouveau type de convertisseur
JsonStringEnumConverter<TEnum>
. La classe existante JsonStringEnumConverter n’est pas prise en charge par l’AOA natif. Vous pouvez annoter vos types enum comme suit :[JsonConverter(typeof(JsonStringEnumConverter<MyEnum>))] public enum MyEnum { Value1, Value2, Value3 } [JsonSerializable(typeof(MyEnum))] public partial class MyContext : JsonSerializerContext { }
Pour plus d’informations, consultez Sérialiser des champs d’énumération sous forme de chaînes.
La nouvelle propriété
JsonConverter.Type
vous permet de rechercher le type d’une instanceJsonConverter
non générique :Dictionary<Type, JsonConverter> CreateDictionary(IEnumerable<JsonConverter> converters) => converters.Where(converter => converter.Type != null) .ToDictionary(converter => converter.Type!);
La propriété peut accepter la valeur Null, car elle retourne
null
pour les instancesJsonConverterFactory
ettypeof(T)
pour les instancesJsonConverter<T>
.
Générateurs de sources de chaîne
La classe JsonSerializerOptions inclut une nouvelle propriété TypeInfoResolverChain qui complète la propriété TypeInfoResolver existante. Ces propriétés sont utilisées dans la personnalisation des contrats pour les générateurs sources de chaînage. L’ajout de la nouvelle propriété signifie que vous n’avez pas besoin de spécifier tous les composants chaînés sur un site d’appel. Ils peuvent être ajoutés après coup. TypeInfoResolverChain vous permet également d’introspecter la chaîne ou d’en supprimer des composants. Pour plus d’informations, consultez Combiner des générateurs de source.
En outre, JsonSerializerOptions.AddContext<TContext>() est désormais obsolète. Elle a été remplacée par les propriétés TypeInfoResolver et TypeInfoResolverChain. Pour plus d’informations, consultez SYSLIB0049.
Hiérarchies d’interface
.NET 8 ajoute la prise en charge de la sérialisation des propriétés à partir des hiérarchies d’interface.
Le code suivant montre un exemple où les propriétés de l’interface implémentée immédiatement et de son interface de base sont sérialisées.
public static void InterfaceHierarchies()
{
IDerived value = new DerivedImplement { Base = 0, Derived = 1 };
string json = JsonSerializer.Serialize(value);
Console.WriteLine(json); // {"Derived":1,"Base":0}
}
public interface IBase
{
public int Base { get; set; }
}
public interface IDerived : IBase
{
public int Derived { get; set; }
}
public class DerivedImplement : IDerived
{
public int Base { get; set; }
public int Derived { get; set; }
}
Stratégies d’affectation de noms
JsonNamingPolicy
inclut de nouvelles stratégies de nommage pour les conversions de noms de propriétés snake_case
(avec trait de soulignement) et kebab-case
(avec trait d’union). Utilisez ces stratégies de la même façon que la stratégie JsonNamingPolicy.CamelCase existante :
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
JsonSerializer.Serialize(new { PropertyName = "value" }, options);
// { "property_name" : "value" }
Pour plus d’informations, consultez Utiliser une stratégie d’attribution de noms intégrée.
Propriétés en lecture seule
Vous pouvez désormais désérialiser sur des champs ou des propriétés en lecture seule (c’est-à-dire ceux qui n’ont pas d’accesseur set
).
Pour choisir cette prise en charge globalement, définissez une nouvelle option, PreferredObjectCreationHandling, sur JsonObjectCreationHandling.Populate. Si la compatibilité vous préoccupe, vous pouvez également activer la fonctionnalité de manière plus granulaire en plaçant l’attribut [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
sur les types spécifiques dont les propriétés doivent être remplies ou sur des propriétés individuelles.
Par exemple, considérez le code suivant qui désérialise dans un type CustomerInfo
qui a deux propriétés en lecture seule.
public static void ReadOnlyProperties()
{
CustomerInfo customer = JsonSerializer.Deserialize<CustomerInfo>("""
{ "Names":["John Doe"], "Company":{"Name":"Contoso"} }
""")!;
Console.WriteLine(JsonSerializer.Serialize(customer));
}
class CompanyInfo
{
public required string Name { get; set; }
public string? PhoneNumber { get; set; }
}
[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
class CustomerInfo
{
// Both of these properties are read-only.
public List<string> Names { get; } = new();
public CompanyInfo Company { get; } = new()
{
Name = "N/A",
PhoneNumber = "N/A"
};
}
Avant .NET 8, les valeurs d’entrée ont été ignorées et les propriétés Names
et Company
ont conservé leurs valeurs par défaut.
{"Names":[],"Company":{"Name":"N/A","PhoneNumber":"N/A"}}
À présent, les valeurs d’entrée sont utilisées pour remplir les propriétés en lecture seule pendant la désérialisation.
{"Names":["John Doe"],"Company":{"Name":"Contoso","PhoneNumber":"N/A"}}
Pour plus d’informations sur le comportement de désérialisation de remplissage, consultez Remplir les propriétés initialisées.
Désactiver la valeur par défaut basée sur la réflexion
Vous pouvez maintenant désactiver l’utilisation du sérialiseur basé sur la réflexion par défaut. Cette désactivation est utile pour éviter l’enracinement accidentel de composants de réflexion qui ne sont même pas en cours d’utilisation, en particulier dans les applications AOA découpées et natives. Pour désactiver la sérialisation basée sur la réflexion par défaut, en exigeant qu’un argument JsonSerializerOptions soit passé aux méthodes de sérialisation et de désérialisation JsonSerializer, définissez la propriété MSBuild JsonSerializerIsReflectionEnabledByDefault
sur la valeur false
dans votre fichier projet.
Utilisez la nouvelle API IsReflectionEnabledByDefault pour vérifier la valeur du commutateur de fonctionnalité. Si vous êtes un auteur de bibliothèque en plus de System.Text.Json, vous pouvez vous appuyer sur la propriété pour configurer vos valeurs par défaut sans racine accidentelle des composants de réflexion.
Pour plus d’informations, consultez Désactiver la valeur par défaut basée de la réflexion.
Nouvelles méthodes d’API JsonNode
Les types JsonNode et System.Text.Json.Nodes.JsonArray incluent les nouvelles méthodes suivantes.
public partial class JsonNode
{
// Creates a deep clone of the current node and all its descendants.
public JsonNode DeepClone();
// Returns true if the two nodes are equivalent JSON representations.
public static bool DeepEquals(JsonNode? node1, JsonNode? node2);
// Determines the JsonValueKind of the current node.
public JsonValueKind GetValueKind(JsonSerializerOptions options = null);
// If node is the value of a property in the parent
// object, returns its name.
// Throws InvalidOperationException otherwise.
public string GetPropertyName();
// If node is the element of a parent JsonArray,
// returns its index.
// Throws InvalidOperationException otherwise.
public int GetElementIndex();
// Replaces this instance with a new value,
// updating the parent object/array accordingly.
public void ReplaceWith<T>(T value);
// Asynchronously parses a stream as UTF-8 encoded data
// representing a single JSON value into a JsonNode.
public static Task<JsonNode?> ParseAsync(
Stream utf8Json,
JsonNodeOptions? nodeOptions = null,
JsonDocumentOptions documentOptions = default,
CancellationToken cancellationToken = default);
}
public partial class JsonArray
{
// Returns an IEnumerable<T> view of the current array.
public IEnumerable<T> GetValues<T>();
}
Membres non publics
Vous pouvez inclure des membres non publics dans le contrat de sérialisation d’un type donné à l’aide d’annotations d’attributs JsonIncludeAttribute et JsonConstructorAttribute.
public static void NonPublicMembers()
{
string json = JsonSerializer.Serialize(new MyPoco(42));
Console.WriteLine(json);
// {"X":42}
JsonSerializer.Deserialize<MyPoco>(json);
}
public class MyPoco
{
[JsonConstructor]
internal MyPoco(int x) => X = x;
[JsonInclude]
internal int X { get; }
}
Pour plus d’informations, consultez Utiliser des types immuables et des membres et accesseurs non publics.
API de désérialisation de diffusion en continu
.NET 8 inclut de nouvelles méthodes d’extension de désérialisation de diffusion en continu IAsyncEnumerable<T>, par exemple GetFromJsonAsAsyncEnumerable. Des méthodes similaires existent qui retournent Task<TResult>, par exemple HttpClientJsonExtensions.GetFromJsonAsync. Les nouvelles méthodes d’extension appellent les API de diffusion en continu et retournent IAsyncEnumerable<T>.
Le code suivant montre comment utiliser les nouvelles méthodes d’extension.
public async static void StreamingDeserialization()
{
const string RequestUri = "https://api.contoso.com/books";
using var client = new HttpClient();
IAsyncEnumerable<Book?> books = client.GetFromJsonAsAsyncEnumerable<Book>(RequestUri);
await foreach (Book? book in books)
{
Console.WriteLine($"Read book '{book?.title}'");
}
}
public record Book(int id, string title, string author, int publishedYear);
Méthode d’extension WithAddedModifier
La nouvelle méthode d’extension WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) vous permet d’introduire facilement des modifications apportées aux contrats de sérialisation d’instances arbitraires IJsonTypeInfoResolver
.
var options = new JsonSerializerOptions
{
TypeInfoResolver = MyContext.Default
.WithAddedModifier(static typeInfo =>
{
foreach (JsonPropertyInfo prop in typeInfo.Properties)
{
prop.Name = prop.Name.ToUpperInvariant();
}
})
};
Nouvelles surcharges JsonContent.Create
Vous pouvez désormais créer des instances JsonContent à l’aide de contrats à découpage sécurisé ou générés par la source. Les nouvelles méthodes sont les suivantes :
- JsonContent.Create(Object, JsonTypeInfo, MediaTypeHeaderValue)
- JsonContent.Create<T>(T, JsonTypeInfo<T>, MediaTypeHeaderValue)
var book = new Book(id: 42, "Title", "Author", publishedYear: 2023);
HttpContent content = JsonContent.Create(book, MyContext.Default.Book);
public record Book(int id, string title, string author, int publishedYear);
[JsonSerializable(typeof(Book))]
public partial class MyContext : JsonSerializerContext
{
}
Geler une instance JsonSerializerOptions
Les nouvelles méthodes suivantes vous permettent de contrôler quand une instance JsonSerializerOptions est gelée :
JsonSerializerOptions.MakeReadOnly()
Cette surcharge est conçue pour être sécurisée en cas de découpage et génère donc une exception dans les cas où l’instance d’options n’a pas été configurée avec un programme de résolution.
JsonSerializerOptions.MakeReadOnly(Boolean)
Si vous passez
true
à cette surcharge, elle remplit l’instance d’options avec le programme de résolution de réflexion par défaut s’il en manque un. Cette méthode est marquéeRequiresUnreferenceCode
/RequiresDynamicCode
et n’est donc pas adaptée aux applications AOT natives.
La nouvelle propriété IsReadOnly vous permet de vérifier si l’instance d’options est gelée.
Abstraction temporelle
La nouvelle classe TimeProvider et l’interface de ITimer ajoutent la fonctionnalité d’abstraction temporelle, ce qui vous permet de simuler le temps dans les scénarios de test. En outre, vous pouvez utiliser l’abstraction de temps pour simuler des opérations de Task qui s’appuient sur la progression du temps à l’aide de Task.Delay et de Task.WaitAsync. L’abstraction temporelle prend en charge les opérations de temps essentielles suivantes :
- Récupérer l’heure locale et UTC
- Obtenir un horodatage pour mesurer les performances
- Créer un retardateur
L’extrait de code suivant présente quelques exemples d’utilisation.
// Get system time.
DateTimeOffset utcNow = TimeProvider.System.GetUtcNow();
DateTimeOffset localNow = TimeProvider.System.GetLocalNow();
TimerCallback callback = s => ((State)s!).Signal();
// Create a timer using the time provider.
ITimer timer = _timeProvider.CreateTimer(
callback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);
// Measure a period using the system time provider.
long providerTimestamp1 = TimeProvider.System.GetTimestamp();
long providerTimestamp2 = TimeProvider.System.GetTimestamp();
TimeSpan period = _timeProvider.GetElapsedTime(providerTimestamp1, providerTimestamp2);
// Create a time provider that works with a
// time zone that's different than the local time zone.
private class ZonedTimeProvider(TimeZoneInfo zoneInfo) : TimeProvider()
{
private readonly TimeZoneInfo _zoneInfo = zoneInfo ?? TimeZoneInfo.Local;
public override TimeZoneInfo LocalTimeZone => _zoneInfo;
public static TimeProvider FromLocalTimeZone(TimeZoneInfo zoneInfo) =>
new ZonedTimeProvider(zoneInfo);
}
Améliorations apportées à UTF8
Si vous souhaitez activer l’écriture d’une représentation de type chaîne de votre type dans une étendue de destination, implémentez la nouvelle interface de IUtf8SpanFormattable sur votre type. Cette nouvelle interface est étroitement liée à ISpanFormattable, mais cible UTF8 et Span<byte>
au lieu de UTF16 et Span<char>
.
IUtf8SpanFormattable a été implémenté sur tous les types primitifs (plus d’autres), avec la même logique partagée que le ciblage string
, Span<char>
ou Span<byte>
. Il prend entièrement en charge tous les formats (y compris le nouveau spécificateur binaire « B ») et toutes les cultures. Cela signifie que vous pouvez désormais mettre en forme directement en UTF8 à partir de Byte
, Complex
, Char
, DateOnly
, DateTime
, DateTimeOffset
, Decimal
, Double
, Guid
, Half
, IPAddress
, IPNetwork
, Int16
, Int32
, Int64
, Int128
, IntPtr
, NFloat
, SByte
, Single
, Rune
, TimeOnly
, TimeSpan
, UInt16
, UInt32
, UInt64
, UInt128
, UIntPtr
, et Version
.
Les nouvelles méthodes Utf8.TryWrite fournissent un équivalent basé sur UTF8 aux méthodes MemoryExtensions.TryWrite existantes, qui sont basées sur UTF16. Vous pouvez utiliser la syntaxe de chaîne interpolée pour mettre en forme une expression complexe directement dans une étendue d’octets UTF8, par exemple :
static bool FormatHexVersion(
short major,
short minor,
short build,
short revision,
Span<byte> utf8Bytes,
out int bytesWritten) =>
Utf8.TryWrite(
utf8Bytes,
CultureInfo.InvariantCulture,
$"{major:X4}.{minor:X4}.{build:X4}.{revision:X4}",
out bytesWritten);
L’implémentation reconnaît IUtf8SpanFormattable sur les valeurs de format et utilise leurs implémentations pour écrire leurs représentations UTF8 directement dans l’étendue de destination.
L’implémentation utilise également la nouvelle méthode Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32), qui, avec son équivalent Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32), prend en charge l’encodage et le décodage dans une étendue de destination. Si l’étendue n’est pas suffisamment longue pour contenir l’état résultant, les méthodes retournent false
plutôt que de lever une exception.
Méthodes pour l’utilisation du caractère aléatoire
Les types System.Random et System.Security.Cryptography.RandomNumberGenerator introduisent deux nouvelles méthodes pour l’utilisation du caractère aléatoire.
GetItems<T>()
Les nouvelles méthodes System.Random.GetItems et System.Security.Cryptography.RandomNumberGenerator.GetItems vous permettent de choisir de manière aléatoire un nombre spécifié d’éléments à partir d’un jeu d’entrée. L’exemple suivant montre comment utiliser System.Random.GetItems<T>()
(sur l’instance fournie par la propriété Random.Shared) pour insérer de manière aléatoire 31 éléments dans un tableau. Cet exemple peut être utilisé dans un jeu de type « Simon » où les joueurs doivent se souvenir d’une séquence de boutons colorés.
private static ReadOnlySpan<Button> s_allButtons = new[]
{
Button.Red,
Button.Green,
Button.Blue,
Button.Yellow,
};
// ...
Button[] thisRound = Random.Shared.GetItems(s_allButtons, 31);
// Rest of game goes here ...
Shuffle<T>()
Les nouvelles méthodes Random.Shuffle et RandomNumberGenerator.Shuffle<T>(Span<T>) vous permettent d’ordonner une étendue de manière aléatoire. Ces méthodes sont utiles pour réduire les biais d’entraînement dans le Machine Learning (la première chose n’est donc pas toujours l’entraînement, et la dernière chose est toujours le test).
YourType[] trainingData = LoadTrainingData();
Random.Shared.Shuffle(trainingData);
IDataView sourceData = mlContext.Data.LoadFromEnumerable(trainingData);
DataOperationsCatalog.TrainTestData split = mlContext.Data.TrainTestSplit(sourceData);
model = chain.Fit(split.TrainSet);
IDataView predictions = model.Transform(split.TestSet);
// ...
Types axés sur les performances
.NET 8 introduit plusieurs nouveaux types visant à améliorer les performances des applications.
Le nouvel espace de noms System.Collections.Frozen inclut les types de collection FrozenDictionary<TKey,TValue> et FrozenSet<T>. Ces types n’autorisent aucune modification des clés et des valeurs une fois la collection créée. Cette exigence permet d’accélérer les opérations de lecture (par exemple,
TryGetValue()
). Ces types sont particulièrement utiles pour les collections qui sont remplies lors de la première utilisation, puis conservées pendant toute la durée d’un service à longue durée de vie, par exemple :private static readonly FrozenDictionary<string, bool> s_configurationData = LoadConfigurationData().ToFrozenDictionary(optimizeForReads: true); // ... if (s_configurationData.TryGetValue(key, out bool setting) && setting) { Process(); }
Les méthodes comme MemoryExtensions.IndexOfAny recherchent la première occurrence de n’importe quelle valeur dans la collection passée. Le nouveau type System.Buffers.SearchValues<T> est conçu pour être passé à de telles méthodes. En conséquence, .NET 8 ajoute de nouvelles surcharges de méthodes comme MemoryExtensions.IndexOfAny qui acceptent une instance du nouveau type. Lorsque vous créez une instance de SearchValues<T>, toutes les données nécessaires pour optimiser les recherches ultérieures sont dérivées à ce moment-là, de sorte que le travail est effectué à l’avance.
Le nouveau type System.Text.CompositeFormat est utile pour optimiser les chaînes de format qui ne sont pas connues au moment de la compilation (par exemple si la chaîne de format est chargée à partir d’un fichier de ressources). Un peu de temps supplémentaire est consacré au départ pour effectuer du travail tel que l’analyse de la chaîne, mais cela évite que le travail ne soit effectué à chaque utilisation.
private static readonly CompositeFormat s_rangeMessage = CompositeFormat.Parse(LoadRangeMessageResource()); // ... static string GetMessage(int min, int max) => string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
Les nouveaux types System.IO.Hashing.XxHash3 et System.IO.Hashing.XxHash128 fournissent des implémentations des algorithmes de hachage rapides XXH3 et XXH128.
System.Numerics et System.Runtime.Intrinsics
Cette section traite des améliorations apportées aux espaces de noms System.Numerics et System.Runtime.Intrinsics.
- Vector256<T>, Matrix3x2 et Matrix4x4 offrent une amélioration de l’accélération matérielle sur .NET 8. Par exemple, Vector256<T> a été réimplémenté de façon à être des opérations
2x Vector128<T>
en interne, si possible. Cela permet l’accélération partielle de certaines fonctions lorsqueVector128.IsHardwareAccelerated == true
maisVector256.IsHardwareAccelerated == false
, par exemple sur Arm64. - Les intrinsèques matérielles sont désormais annotées avec l’attribut
ConstExpected
. Cela garantit que les utilisateurs savent quand le matériel sous-jacent attend une constante, et par conséquent quand une valeur non constante peut nuire de manière inattendue aux performances. - L’API Lerp(TSelf, TSelf, TSelf)
Lerp
a été ajoutée à IFloatingPointIeee754<TSelf> et par conséquent àfloat
(Single),double
(Double) et Half. Cette API permet d’effectuer efficacement et correctement une interpolation linéaire entre deux valeurs.
Vector512 et AVX-512
La prise en charge SIMD étendue de .NET Core 3.0 pour inclure les API d’intrinsèques matérielles spécifiques à la plateforme pour x86/x64. .NET 5 a ajouté la prise en charge d’Arm64 et .NET 7 à l’intrinsèque matériel multiplateforme. .NET 8 renforce la prise en charge de SIMD en introduisant Vector512<T> et en prenant en charge les instructions d’Intel Advanced Vector Extensions 512 (AVX-512).
Plus précisément, .NET 8 inclut la prise en charge des fonctionnalités clés suivantes d’AVX-512 :
- Opérations vectorielles 512 bits
- 16 registres SIMD supplémentaires
- Instructions supplémentaires disponibles pour les vecteurs 128 bits, 256 bits et 512 bits
Si vous avez du matériel qui prend en charge les fonctionnalités, Vector512.IsHardwareAccelerated signale désormais true
.
.NET 8 ajoute également plusieurs classes spécifiques à la plateforme sous l’espace de noms System.Runtime.Intrinsics.X86 :
- Avx512F (fondamental)
- Avx512BW (octet et mot)
- Avx512CD (détection de conflit)
- Avx512DQ (doubleword et quadword)
- Avx512Vbmi (instructions de manipulation d’octet vectoriel)
Ces classes suivent la même forme générale que les autres architectures de jeu d’instructions (ISA) dans lesquelles elles exposent une propriété IsSupported et une classe de Avx512F.X64 imbriquée pour obtenir des instructions disponibles uniquement pour les processus 64 bits. En outre, chaque classe a une classe Avx512F.VL imbriquée qui expose les extensions Avx512VL
(longueur vectorielle) pour le jeu d’instructions correspondant.
Même si vous n'utilisez pas explicitement d'instructions Vector512
ou Avx512F
dans votre code, vous bénéficierez probablement de la nouvelle prise en charge de l'AVX-512. Le JIT peut tirer parti des registres et instructions supplémentaires implicitement lors de l’utilisation Vector128<T> ou Vector256<T>. La bibliothèque de classes de base utilise ces intrinsèques matérielles en interne dans la plupart des opérations exposées par Span<T> et ReadOnlySpan<T> dans de nombreuses API mathématiques exposées pour les types primitifs.
Validation des données
L’espace de noms System.ComponentModel.DataAnnotations inclut de nouveaux attributs de validation des données destinés aux scénarios de validation dans les services natifs cloud. Si les validateurs DataAnnotations
préexistants sont destinés à la validation traditionnelle de l’entrée de données dans l’interface utilisateur, comme les champs d’un formulaire, les nouveaux attributs sont conçus pour valider des données qui ne sont pas entrées par l’utilisateur, comme les options de configuration. En plus des nouveaux attributs, de nouvelles propriétés ont été ajoutées aux types RangeAttribute et RequiredAttribute.
Nouvelle API | Description |
---|---|
RangeAttribute.MinimumIsExclusive RangeAttribute.MaximumIsExclusive |
Spécifie si les limites sont incluses dans la plage autorisée. |
System.ComponentModel.DataAnnotations.LengthAttribute | Spécifie les limites inférieures et supérieures pour des chaînes ou des collections. Par exemple, [Length(10, 20)] spécifie au moins 10 éléments et au plus 20 éléments dans une collection. |
System.ComponentModel.DataAnnotations.Base64StringAttribute | Vérifie qu’une chaîne est une représentation en Base64 valide. |
System.ComponentModel.DataAnnotations.AllowedValuesAttribute System.ComponentModel.DataAnnotations.DeniedValuesAttribute |
Spécifie des listes d’autorisation et des listes d’exclusion, respectivement. Par exemple : [AllowedValues("apple", "banana", "mango")] . |
Mesures
Les nouvelles API vous permettent d’attacher des balises de paire clé-valeur à des objets Meter et Instrument lorsque vous les créez. Les agrégateurs de mesures de métriques publiées peuvent utiliser les balises pour différencier les valeurs agrégées.
var options = new MeterOptions("name")
{
Version = "version",
// Attach these tags to the created meter.
Tags = new TagList()
{
{ "MeterKey1", "MeterValue1" },
{ "MeterKey2", "MeterValue2" }
}
};
Meter meter = meterFactory!.Create(options);
Counter<int> counterInstrument = meter.CreateCounter<int>(
"counter", null, null, new TagList() { { "counterKey1", "counterValue1" } }
);
counterInstrument.Add(1);
Les nouvelles API sont les suivantes :
- MeterOptions
- Meter(MeterOptions)
- CreateCounter<T>(String, String, String, IEnumerable<KeyValuePair<String,Object>>)
Chiffrement
.NET 8 ajoute la prise en charge des primitives de hachage SHA-3. (SHA-3 est actuellement pris en charge par Linux avec OpenSSL 1.1.1 ou version ultérieure et Windows 11 build 25324 ou version ultérieure.) Les API où SHA-2 est disponible offrent désormais un complément SHA-3. Cela inclut SHA3_256
, SHA3_384
et SHA3_512
pour le hachage ; HMACSHA3_256
, HMACSHA3_384
et HMACSHA3_512
pour HMAC ; HashAlgorithmName.SHA3_256
, HashAlgorithmName.SHA3_384
et HashAlgorithmName.SHA3_512
pour le hachage où l’algorithme est configurable ; et RSAEncryptionPadding.OaepSHA3_256
, RSAEncryptionPadding.OaepSHA3_384
et RSAEncryptionPadding.OaepSHA3_512
pour le chiffrement RSA OAEP.
L’exemple suivant montre comment utiliser les API, y compris la propriété SHA3_256.IsSupported
pour déterminer si la plateforme prend en charge SHA-3.
// Hashing example
if (SHA3_256.IsSupported)
{
byte[] hash = SHA3_256.HashData(dataToHash);
}
else
{
// ...
}
// Signing example
if (SHA3_256.IsSupported)
{
using ECDsa ec = ECDsa.Create(ECCurve.NamedCurves.nistP256);
byte[] signature = ec.SignData(dataToBeSigned, HashAlgorithmName.SHA3_256);
}
else
{
// ...
}
La prise en charge de SHA-3 est actuellement destinée à prendre en charge les primitives de chiffrement. Les constructions et les protocoles de niveau supérieur ne sont pas censés prendre entièrement en charge SHA-3 initialement. Ces protocoles incluent les certificats X.509, SignedXml, et COSE.
Réseau
Prise en charge du proxy HTTPS
Jusqu’à présent, les types de proxy que HttpClient prenait en charge ont tous autorisé un « intermédiaire » à voir à quel site le client se connecte, même pour les URI HTTPS. HttpClient prend désormais en charge le proxy HTTPS, qui crée un canal chiffré entre le client et le proxy afin que toutes les requêtes puissent être gérées avec une confidentialité complète.
Pour activer le proxy HTTPS, définissez la variable d’environnement all_proxy
ou utilisez la classe WebProxy pour contrôler le proxy par programmation.
Unix : export all_proxy=https://x.x.x.x:3218
Windows : set all_proxy=https://x.x.x.x:3218
Vous pouvez également utiliser la classe WebProxy pour contrôler le proxy par programmation.
Méthodes ZipFile basées sur le flux
.NET 8 inclut de nouvelles surcharges de ZipFile.CreateFromDirectory qui vous permettent de collecter tous les fichiers inclus dans un répertoire et de les compresser, puis de stocker le fichier zip résultant dans le flux fourni. De même, les nouvelles surcharges ZipFile.ExtractToDirectory vous permettent de fournir un flux contenant un fichier compressé et d’extraire son contenu dans le système de fichiers. Voici les nouvelles surcharges :
namespace System.IO.Compression;
public static partial class ZipFile
{
public static void CreateFromDirectory(
string sourceDirectoryName, Stream destination);
public static void CreateFromDirectory(
string sourceDirectoryName,
Stream destination,
CompressionLevel compressionLevel,
bool includeBaseDirectory);
public static void CreateFromDirectory(
string sourceDirectoryName,
Stream destination,
CompressionLevel compressionLevel,
bool includeBaseDirectory,
Encoding? entryNameEncoding);
public static void ExtractToDirectory(
Stream source, string destinationDirectoryName) { }
public static void ExtractToDirectory(
Stream source, string destinationDirectoryName, bool overwriteFiles) { }
public static void ExtractToDirectory(
Stream source, string destinationDirectoryName, Encoding? entryNameEncoding) { }
public static void ExtractToDirectory(
Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles) { }
}
Ces nouvelles API peuvent être utiles lorsque l’espace disque est limité, car elles évitent d’avoir à utiliser le disque comme étape intermédiaire.
Bibliothèques d’extensions
Cette section comprend les sous-rubriques suivantes :
- Validation des options
- Constructeurs LoggerMessageAttribute
- Métriques d’extensions
- Services de cycle de vie hébergés
- Services d’injection de dépendances (DI) à clé
- System.Numerics.Tensors.TensorPrimitives
Services d’injection de dépendances (DI) à clé
Les services d’injection de dépendances (DI) à clé fournissent les moyens pour inscrire et récupérer des services d’injection de dépendances en utilisant des clés. En utilisant des clés, vous pouvez mesurez l’étendue de l’inscription et de la consommation de services. Voici quelques-unes des nouvelles API :
- L'interface IKeyedServiceProvider.
- L’attribut ServiceKeyAttribute, qui peut être utilisé pour injecter la clé utilisée pour l’inscription/la résolution dans le constructeur.
- L’attribut FromKeyedServicesAttribute, qui peut être utilisé sur les paramètres du constructeur de service pour spécifier le service à clé à utiliser.
- Différentes nouvelles méthodes d’extension pour que IServiceCollection prenne en charge les services à clé, par exemple ServiceCollectionServiceExtensions.AddKeyedScoped.
- L’implémentation ServiceProvider de IKeyedServiceProvider.
L’exemple suivant vous montre comment utiliser des services DI à clé.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<BigCacheConsumer>();
builder.Services.AddSingleton<SmallCacheConsumer>();
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
WebApplication app = builder.Build();
app.MapGet("/big", (BigCacheConsumer data) => data.GetData());
app.MapGet("/small", (SmallCacheConsumer data) => data.GetData());
app.MapGet("/big-cache", ([FromKeyedServices("big")] ICache cache) => cache.Get("data"));
app.MapGet("/small-cache", (HttpContext httpContext) => httpContext.RequestServices.GetRequiredKeyedService<ICache>("small").Get("data"));
app.Run();
class BigCacheConsumer([FromKeyedServices("big")] ICache cache)
{
public object? GetData() => cache.Get("data");
}
class SmallCacheConsumer(IServiceProvider serviceProvider)
{
public object? GetData() => serviceProvider.GetRequiredKeyedService<ICache>("small").Get("data");
}
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
Pour plus d’informations, consultez dotnet/runtime#64427.
Services de cycle de vie hébergés
Les services hébergés ont désormais davantage d’options d’exécution pendant le cycle de vie de l’application. IHostedService fournissait StartAsync
et StopAsync
, et maintenant IHostedLifecycleService fournit ces méthodes supplémentaires :
- StartingAsync(CancellationToken)
- StartedAsync(CancellationToken)
- StoppingAsync(CancellationToken)
- StoppedAsync(CancellationToken)
Ces méthodes s’exécutent avant et après les points existants respectivement.
L’exemple suivant montre comment utiliser les nouvelles API.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
internal class HostedLifecycleServices
{
public async static void RunIt()
{
IHostBuilder hostBuilder = new HostBuilder();
hostBuilder.ConfigureServices(services =>
{
services.AddHostedService<MyService>();
});
using (IHost host = hostBuilder.Build())
{
await host.StartAsync();
}
}
public class MyService : IHostedLifecycleService
{
public Task StartingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StartAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StartedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StoppedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StoppingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
}
}
Pour plus d’informations, consultez dotnet/runtime#86511.
Validation des options
Générateur de source
Pour réduire la surcharge au démarrage et améliorer l’ensemble de fonctionnalités de validation, nous avons introduit un générateur de code source qui implémente la logique de validation. Le code suivant montre des exemples de modèles et de classes de validateurs.
public class FirstModelNoNamespace
{
[Required]
[MinLength(5)]
public string P1 { get; set; } = string.Empty;
[Microsoft.Extensions.Options.ValidateObjectMembers(
typeof(SecondValidatorNoNamespace))]
public SecondModelNoNamespace? P2 { get; set; }
}
public class SecondModelNoNamespace
{
[Required]
[MinLength(5)]
public string P4 { get; set; } = string.Empty;
}
[OptionsValidator]
public partial class FirstValidatorNoNamespace
: IValidateOptions<FirstModelNoNamespace>
{
}
[OptionsValidator]
public partial class SecondValidatorNoNamespace
: IValidateOptions<SecondModelNoNamespace>
{
}
Si votre application utilise l’injection de dépendances, vous pouvez injecter la validation comme indiqué dans l’exemple de code suivant.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<FirstModelNoNamespace>(
builder.Configuration.GetSection("some string"));
builder.Services.AddSingleton<
IValidateOptions<FirstModelNoNamespace>, FirstValidatorNoNamespace>();
builder.Services.AddSingleton<
IValidateOptions<SecondModelNoNamespace>, SecondValidatorNoNamespace>();
Type ValidateOptionsResultBuilder
.NET 8 introduit le type ValidateOptionsResultBuilder pour faciliter la création d’un objet ValidateOptionsResult. Il est important de noter que ce générateur permet l’accumulation de plusieurs erreurs. Auparavant, la création de l’objet ValidateOptionsResult nécessaire à l’implémentation de IValidateOptions<TOptions>.Validate(String, TOptions) était difficile et a parfois entraîné des erreurs de validation en couches. S’il y avait plusieurs erreurs, le processus de validation s’arrêtait souvent à la première erreur.
L’extrait de code suivant affiche un exemple d’utilisation de ValidateOptionsResultBuilder.
ValidateOptionsResultBuilder builder = new();
builder.AddError("Error: invalid operation code");
builder.AddResult(ValidateOptionsResult.Fail("Invalid request parameters"));
builder.AddError("Malformed link", "Url");
// Build ValidateOptionsResult object has accumulating multiple errors.
ValidateOptionsResult result = builder.Build();
// Reset the builder to allow using it in new validation operation.
builder.Clear();
Constructeurs LoggerMessageAttribute
LoggerMessageAttribute offre maintenant des surcharges de constructeur supplémentaires. Auparavant, vous deviez choisir le constructeur sans paramètre ou le constructeur qui nécessitait tous les paramètres (ID d’événement, niveau de journal et message). Les nouvelles surcharges offrent une plus grande flexibilité dans la spécification des paramètres requis avec un code réduit. Si vous ne fournissez pas d’ID d’événement, le système en génère un automatiquement.
public LoggerMessageAttribute(LogLevel level, string message);
public LoggerMessageAttribute(LogLevel level);
public LoggerMessageAttribute(string message);
Métriques d’extensions
Interface IMeterFactory
Vous pouvez inscrire la nouvelle interface IMeterFactory dans des conteneurs d’injection de dépendances (DI) et l’utiliser pour créer des objets Meter de manière isolée.
Inscrivez l’interface IMeterFactory dans le conteneur DI à l’aide de l’implémentation de fabrique de compteur par défaut :
// 'services' is the DI IServiceCollection.
services.AddMetrics();
Les consommateurs peuvent alors obtenir la fabrique de compteur et l’utiliser pour créer un objet Meter.
IMeterFactory meterFactory = serviceProvider.GetRequiredService<IMeterFactory>();
MeterOptions options = new MeterOptions("MeterName")
{
Version = "version",
};
Meter meter = meterFactory.Create(options);
Classe MetricCollector<T>
La nouvelle classe MetricCollector<T> vous permet d’enregistrer des mesures de métriques ainsi que des horodatages. En outre, la classe offre la possibilité d’utiliser un fournisseur de temps de votre choix pour une génération d’horodatage précise.
const string CounterName = "MyCounter";
DateTimeOffset now = DateTimeOffset.Now;
var timeProvider = new FakeTimeProvider(now);
using var meter = new Meter(Guid.NewGuid().ToString());
Counter<long> counter = meter.CreateCounter<long>(CounterName);
using var collector = new MetricCollector<long>(counter, timeProvider);
Assert.IsNull(collector.LastMeasurement);
counter.Add(3);
// Verify the update was recorded.
Assert.AreEqual(counter, collector.Instrument);
Assert.IsNotNull(collector.LastMeasurement);
Assert.AreSame(collector.GetMeasurementSnapshot().Last(), collector.LastMeasurement);
Assert.AreEqual(3, collector.LastMeasurement.Value);
Assert.AreEqual(now, collector.LastMeasurement.Timestamp);
System.Numerics.Tensors.TensorPrimitives
Le package NuGet System.Numerics.Tensors mis à jour inclut des API dans le nouvel espace de noms TensorPrimitives qui ajoutent la prise en charge des opérations de tenseur. Les primitives de tenseur optimisent les charges de travail gourmandes en données comme celles de l’IA et du Machine Learning.
Les charges de travail d’IA telles que la recherche sémantique et la génération augmentée par récupération (RAG) étendent les fonctionnalités en langage naturel des grands modèles de langage tels que ChatGPT en augmentant les invites avec des données pertinentes. Pour ces charges de travail, les opérations sur les vecteurs, comme la similarité cosinus permettant de trouver les données les plus pertinentes pour répondre à une question, sont cruciales. Le package System.Numerics.Tensors.TensorPrimitives fournit des API pour les opérations vectorielles, ce qui signifie que vous n’avez pas besoin de prendre une dépendance externe ou d’écrire votre propre implémentation.
Ce package remplace le package System.Numerics.Tensors.
Pour plus d’informations, consultez le billet de blog Announcing .NET 8 RC 2.