Nouveautés du runtime .NET 8
Cet article décrit les nouvelles fonctionnalités du runtime .NET pour .NET 8.
Améliorations 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 de l'architecture Arm64
- Améliorations de SIMD
- Prise en charge des extensions ISA AVX-512 (voir Vector512 et AVX-512)
- Améliorations natives du 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 dispose de deux instructions pour la recherche de vecteurs dans une table, nécessitant que toutes les entités de leurs opérandes en tuple soient présentes dans des registres consécutifs.
- JIT/NativeAOT peut désormais désinscrire et vectoriser automatiquement certaines opérations de mémoire avec SIMD, telles que la comparaison, la copie et la mise à zéro, si elle peut déterminer leurs tailles au moment de la compilation.
En outre, l’optimisation guidée par profil dynamique (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 runtime pour l’activer. Le PGO 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 tier 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 raisonner et optimiser plus précisément.
Le JIT a déjà pris en charge cette optimisation, mais avec plusieurs limitations importantes, notamment :
- Elle n’a été prise en charge que pour les structures comportant quatre champs ou moins.
- Elle n’a été 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 de longue date.
Collecte des ordures
.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 rentable, les services doivent augmenter et diminuer la consommation des ressources à mesure que la demande varie. Lorsqu’un service détecte une diminution de la demande, il peut réduire la consommation des ressources en réduisant sa limite de mémoire. Auparavant, cela échouerait, car le garbage collector (GC) n’était pas conscient de 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 limitations à prendre en compte :
- Sur les plateformes 32 bits (par exemple, Windows x86 et Linux ARM), .NET ne peut pas établir une nouvelle limite fixe 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 la réduction est trop agressive et ne laisse pas de place au GC de manœuvrer. 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 la limite.
L’extrait de code suivant montre comment appeler 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 envoyer un InvalidOperationException si la limite dure n'est pas valide, par exemple, dans le cas de pourcentages négatifs de limite dure de tas et si la limite dure est trop basse. Cela peut se produire si la limite dure du tas que l'actualisation définira, soit en raison des nouveaux paramètres des AppData, soit en raison des modifications de la limite de mémoire du conteneur, est inférieure à ce qui a déjà été engagé.
Globalisation des applications mobiles
Les applications mobiles sur iOS, tvOS et MacCatalyst peuvent opter pour un nouveau mode de globalisation hybride qui utilise un bundle ICU plus léger. En mode hybride, les données de globalisation sont partiellement extraites du bundle d’ICU et partiellement des appels dans des API natives. Le mode hybride dessert toutes les localités prises en charge par les applications mobiles.
Le mode hybride est le plus adapté aux applications qui ne peuvent pas fonctionner en mode de globalisation invariante et qui utilisent des cultures qui ont été supprimées des données iCU sur mobile. Vous pouvez également l’utiliser lorsque vous souhaitez charger un fichier de données DCU 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é HybridGlobalization
MSBuild sur true :
<PropertyGroup>
<HybridGlobalization>true</HybridGlobalization>
</PropertyGroup>
Il existe certaines limitations à prendre en compte :
- 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 comportementales.
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 la 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 à partir du code C# vers du code non managé. Il génère également du code pour activer l’appel à partir de code non managé en C#. Ce générateur de source s'intègre à LibraryImportAttribute, et vous pouvez utiliser des types avec GeneratedComInterfaceAttribute comme paramètres et types de retour dans les méthodes dotées de l'attribut 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 au 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 GeneratedComInterface
-attributed et les types GeneratedComClass
-attributed.
Si votre code C# utilise uniquement une interface GeneratedComInterface
-attributed pour encapsuler un objet COM à partir d’un code non managé ou encapsuler un objet managé à partir de C# pour exposer au code non managé, vous pouvez utiliser les options de la propriété Options pour personnaliser le code qui sera généré. Grâce à ces options, vous n'avez pas besoin d'écrire des marshallers pour des scénarios dont vous savez qu'ils ne seront pas utilisés.
Le générateur source utilise le nouveau type de 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 l’interopérabilité COM, tout en fournissant des points de personnalisation pour les utilisateurs avancés. Si votre application possède 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 source ne prend pas actuellement en charge, envisagez d'utiliser le nouveau type StrategyBasedComWrappers pour ajouter les fonctionnalités manquantes à votre scénario et obtenir la même expérience utilisateur .NET pour vos types COM.
Si vous utilisez Visual Studio, de 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 avec le numéro ComImportAttribute, une ampoule offre une option de conversion en interopération générée par le code source. Le correctif modifie l’interface pour utiliser l’attribut GeneratedComInterfaceAttribute. En regard 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 modifier vos méthodes de DllImport
pour utiliser LibraryImportAttribute
.
Limitations
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 préexistante basée sur la réflexion.
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 de framework 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 compilées par AOT, et quand PublishTrimmed
est défini sur true
(applications .NET 8+). Pour les autres types de projet, le générateur source est désactivé par défaut, mais vous pouvez choisir de définir 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 contient les sous-rubriques suivantes :
- Réflexion
- Sérialisation
- Abstraction temporelle
- Améliorations du 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 un flux
Réflexion
Les pointeurs de fonction ont été introduits dans .NET 5, mais la prise en charge adéquate 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 fournit l’accès aux métadonnées du pointeur de fonction, notamment les conventions d’appel, le type de retour et les paramètres.
Remarque
Une instance de pointeur de fonction, qui est une adresse physique à une fonction, continue d’être représentée en tant que IntPtr. Seul le type de réflexion a changé.
La nouvelle fonctionnalité est actuellement implémentée uniquement dans le runtime CoreCLR et MetadataLoadContext.
De nouvelles API ont été ajoutées à System.Type, telles que IsFunctionPointer, et à System.Reflection.PropertyInfo, System.Reflection.FieldInfoet System.Reflection.ParameterInfo. Le code suivant montre comment utiliser certaines 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
De nombreuses améliorations ont été apportées aux fonctionnalités de sérialisation et de désérialisation de System.Text.Json dans .NET 8. Par exemple, vous pouvez personnaliser la gestion des propriétés JSON qui ne figurent pas dans le POCO.
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
- Politiques de dénomination
- Propriétés en lecture seule
- Désactiver les paramètres par défaut basés sur la réflexion
- nouvelles méthodes d’API JsonNode
- Membres non publics
- API de désérialisation en continu
- Méthode d'extension WithAddedModifier
- Nouvelles surcharges JsonContent.Create
- Gel d'une instance de 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.
Half, Int128et UInt128 types numériques.
Console.WriteLine(JsonSerializer.Serialize( [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ] )); // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
Memory<T> et les valeurs ReadOnlyMemory<T>. Les valeurs
byte
sont sérialisées sous forme de chaînes Base64 et les autres types sous forme de 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 source
.NET 8 inclut des améliorations du générateur de source System.Text.Json qui visent à rendre l'expérience Native AOT équivalente à celle du sérialiseur basé sur la réflexion. Par exemple:
Le générateur source prend désormais en charge la sérialisation des types avec des propriétés
required
etinit
. Ces deux éléments ont déjà été 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.
la parité des fonctionnalités JsonSourceGenerationOptionsAttribute avec JsonSerializerOptions. Pour plus d’informations, consultez Spécifier des options (génération de source).
Diagnostics supplémentaires (tels que SYSLIB1034 et SYSLIB1039).
N’incluez pas de 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 une résolution proche de l’ancêtre 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 JsonStringEnumConverter existante n’est pas prise en charge dans L’AOT natif. Vous pouvez annoter vos types d’énumération 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 les champs d’énumération sous forme de chaînes.
La nouvelle propriété
JsonConverter.Type
vous permet de rechercher le type d’une instance deJsonConverter
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 de supprimer des composants. Pour plus d'informations, consultez la section Combiner les générateurs de sources.
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 de hiérarchies d’interface.
Le code suivant montre un exemple où les propriétés de l’interface immédiatement implémentée 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; }
}
Politiques de dénomination
JsonNamingPolicy
inclut de nouvelles stratégies de nommage pour les conversions de noms de propriétés snake_case
(avec un trait de soulignement) et kebab-case
(avec un trait d'union). Utilisez ces stratégies de la même façon que la stratégie de JsonNamingPolicy.CamelCase existante :
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
JsonSerializer.Serialize(new { PropertyName = "value" }, options);
// { "property_name" : "value" }
Pour plus d’informations, consultez Utilisez une politique de nommage 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 opter globalement pour cette prise en charge, définissez une nouvelle option, PreferredObjectCreationHandling, à JsonObjectCreationHandling.Populate. Si la compatibilité est un problème, vous pouvez également activer la fonctionnalité de manière plus granulaire en plaçant l’attribut [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
sur des 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 populate, 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 utilisés, en particulier dans les applications AOT trimmé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 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, voir Désactiver les propriétés par défaut 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 streaming
.NET 8 inclut de nouvelles méthodes d’extension de désérialisation de flux IAsyncEnumerable<T>, par exemple GetFromJsonAsAsyncEnumerable. Des méthodes similaires ont existé qui retournent Task<TResult>, par exemple, HttpClientJsonExtensions.GetFromJsonAsync. Les nouvelles méthodes d’extension appellent des 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 des contrats de sérialisation des instances arbitraires de 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 maintenant créer des instances JsonContent en utilisant des contrats trim-safe ou générés à 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 de JsonSerializerOptions
Les nouvelles méthodes suivantes vous permettent de contrôler quand une instance de JsonSerializerOptions est figée :
JsonSerializerOptions.MakeReadOnly()
Cette surcharge est conçue pour être supprimée 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 résolveur 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 figée.
Abstraction temporelle
La nouvelle classe TimeProvider et l’interface ITimer ajoutent des fonctionnalités d’abstraction du temps et, 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 minuteur
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 ainsi que d'autres, avec la même logique partagée pour le ciblage de 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
au lieu de lever une exception.
Méthodes de travail avec l'aléatoire
Les types System.Random et System.Security.Cryptography.RandomNumberGenerator introduisent deux nouvelles méthodes pour travailler avec la randomité.
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 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 façon aléatoire 31 éléments dans un tableau. Cet exemple peut être utilisé dans un jeu de « Simon » où les joueurs doivent mémoriser 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 de rendre aléatoire l'ordre d'une séquence. Ces méthodes sont utiles pour réduire le biais d’apprentissage dans le Machine Learning (donc la première chose n’est pas toujours l’entraînement, et la dernière chose toujours tester).
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 qu’une collection est créée. Cette exigence permet des opérations de lecture plus rapides (par exemple,
TryGetValue()
). Ces types sont particulièrement utiles pour les collections remplies lors de la première utilisation, puis conservées pendant la durée d’un service de longue durée, par exemple :private static readonly FrozenDictionary<string, bool> s_configurationData = LoadConfigurationData().ToFrozenDictionary(); // ... if (s_configurationData.TryGetValue(key, out bool setting) && setting) { Process(); }
Les méthodes comme MemoryExtensions.IndexOfAny recherchent la première occurrence d'une valeur dans la collection transmise. 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 suivantes sont dérivées à ce moment, ce qui signifie 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 passé à l’avance pour effectuer des tâches telles que l’analyse de la chaîne de caractères, mais cela permet d'éviter d'avoir à être refait à 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 XXH3 et XXH128 rapides.
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>, Matrix3x2et Matrix4x4 ont amélioré l’accélération matérielle sur .NET 8. Par exemple, Vector256<T> a été réimplémenté en interne pour correspondre à des opérations
2x Vector128<T>
, dans la mesure du possible. Cela permet une accélération partielle de certaines fonctions lorsqueVector128.IsHardwareAccelerated == true
maisVector256.IsHardwareAccelerated == false
, comme sur Arm64. - Les intrinsèques matérielles sont désormais annotées avec l’attribut
ConstExpected
. Cela garantit que les utilisateurs sont conscients du moment où le matériel sous-jacent attend une constante et, par conséquent, lorsqu’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 de .NET Core 3.0 a été étendue 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 a ajouté les intrinsèques matérielles multiplateformes. .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 de 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 prenant en charge la fonctionnalité, alors Vector512.IsHardwareAccelerated signale maintenant 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 des conflits)
- Avx512DQ (doubleword et quadword)
- Avx512Vbmi (instructions de manipulation d’octets vectoriels)
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 les 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 des instructions spécifiques à Vector512
ou des instructions spécifiques à Avx512F
dans votre code, vous bénéficierez probablement de la nouvelle prise en charge AVX-512. Le JIT peut tirer parti des registres et instructions supplémentaires implicitement lors de l’utilisation de Vector128<T> ou de Vector256<T>. La bibliothèque de classes de base utilise ces intrinsèques matériels en interne dans la plupart des opérations exposées par Span<T> et ReadOnlySpan<T> et dans la plupart des 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 de données destinés aux scénarios de validation dans les services cloud natifs. Bien que les validateurs de DataAnnotations
préexistants soient destinés à la validation classique de l’entrée de données de l’interface utilisateur, comme les champs d’un formulaire, les nouveaux attributs sont conçus pour valider les données d’entrée non utilisateur, telles que les options de configuration . Outre les nouveaux attributs, de nouvelles propriétés ont été ajoutées au type RangeAttribute.
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)] nécessite au moins 10 éléments et au plus 20 éléments d’une collection. |
System.ComponentModel.DataAnnotations.Base64StringAttribute | Valide qu’une chaîne est une représentation Base64 valide. |
System.ComponentModel.DataAnnotations.AllowedValuesAttribute System.ComponentModel.DataAnnotations.DeniedValuesAttribute |
Spécifiez les listes d’autorisation et les listes de refus, respectivement. Par exemple, [AllowedValues("apple", "banana", "mango")] . |
Métriques
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>>)
Cryptographie
.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 compliment 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 vise actuellement à prendre en charge les primitives de chiffrement. Les constructions et protocoles de niveau supérieur ne sont pas censés prendre entièrement en charge SHA-3 initialement. Ces protocoles incluent des certificats X.509, SignedXmlet COSE.
Réseautage
Prise en charge du proxy HTTPS
Jusqu’à présent, les types de proxy qui HttpClient pris en charge ont tous autorisé un « man-in-the-middle » à voir le site auquel le client se connecte, même pour les URI HTTPS. HttpClient prend désormais en charge proxy HTTPS, ce 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é totale.
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 ils évitent d’avoir à utiliser le disque comme étape intermédiaire.
Bibliothèques d’extensions
Cette section contient 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é permettent d'enregistrer et de récupérer des services DI à l'aide de clés. En utilisant des clés, vous pouvez étendre la façon dont vous inscrivez et consommez des 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.
- 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 les 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 fourni 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 source
Pour réduire la surcharge de démarrage et améliorer le jeu 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 validateur.
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 requis pour implémenter IValidateOptions<TOptions>.Validate(String, TOptions) était difficile et provoquait parfois des erreurs de validation multiples. S’il y avait plusieurs erreurs, le processus de validation s’est souvent arrêté lors de la première erreur.
L’extrait de code suivant montre 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 La méthode d'injection de dépendances par clé 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 tout en réduisant le code. 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 de 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 ensuite se procurer l'usine de compteurs et l’utiliser pour créer un nouvel 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
La mise à jour du package NuGet System.Numerics.Tensors inclut des API dans le nouveau type System.Numerics.Tensors.TensorPrimitives qui ajoutent la prise en charge des opérations de tenseurs. Les primitives de capteur optimisent les charges de travail gourmandes en données comme celles de l’IA et du Machine Learning.
Les charges de travail IA telles que la recherche sémantique et la génération augmentée par récupération (RAG) étendent les fonctionnalités de langage naturel des larges modèles de langage, tel que ChatGPT, en enrichissant les requêtes avec des données pertinentes. Pour ces charges de travail, les opérations sur les vecteurs, comme similarité cosinus pour trouver les données les plus pertinentes pour répondre à une question, sont cruciales. Le type TensorPrimitives fournit des API pour les opérations vectorielles.
Pour plus d'informations, consultez l'article de blog Announcing XXX.NET 8 RC 2.
Prise en charge native d’AOA
L'option de publication en tant qu'AOT native a été introduite pour la première fois dans XXX.NET 7. La publication d’une application avec AOT natif crée une version autonome de votre application qui n’a pas besoin d’un runtime. Tout est inclus dans un seul fichier. .NET 8 apporte les améliorations suivantes à la publication AOT native :
Ajoute la prise en charge des architectures x64 et Arm64 sur macOS.
Réduit les tailles des applications AOT natives sur Linux jusqu’à 50%. Le tableau suivant montre la taille d’une application « Hello World » publiée avec L’AOT natif qui inclut l’intégralité du runtime .NET sur .NET 7 et .NET 8 :
Système d’exploitation .NET 7 .NET 8 Linux x64 (avec -p:StripSymbols=true
)3,76 Mo 1,84 Mo Windows x64 2,85 Mo 1,77 Mo Vous permet de spécifier une préférence d’optimisation : taille ou vitesse. Par défaut, le compilateur choisit de générer du code rapide tout en étant attentif à la taille de l’application. Toutefois, vous pouvez utiliser la propriété MSBuild
<OptimizationPreference>
pour optimiser spécifiquement l’un ou l’autre. Pour plus d’informations, consultez Optimiser les déploiements AOT.
Cibler des plateformes similaires à iOS avec Native AOT
.NET 8 entame le processus pour activer la prise en charge Native AOT pour les plateformes similaires à iOS. Vous pouvez maintenant générer et exécuter des applications .NET iOS et .NET MAUI avec AOT natif sur les plateformes suivantes :
ios
iossimulator
maccatalyst
tvos
tvossimulator
Les tests préliminaires montrent que la taille de l’application sur le disque diminue d’environ 35% pour les applications iOS .NET qui utilisent native AOT au lieu de Mono. La taille de l'application sur le disque pour les applications iOS XXX.NET MAUI diminue jusqu'à 50 %. En outre, le temps de démarrage est également plus rapide. Les applications iOS .NET ont un temps de démarrage environ 28% % plus rapide, tandis que les applications iOS .NET MAUI ont environ 50% % meilleures performances au démarrage par rapport à Mono. La prise en charge de .NET 8 est expérimentale et seule la première étape de la fonctionnalité dans son ensemble. Pour plus d’informations, consultez le billet de blog .NET 8 Performance Improvements in .NET MAUI.
La prise en charge native d’AOT est disponible en tant que fonctionnalité d’opt-in destinée au déploiement d’applications ; Mono est toujours le runtime par défaut pour le développement et le déploiement d’applications. Pour générer et exécuter une application .NET MAUI avec AOT natif sur un appareil iOS, utilisez dotnet workload install maui
pour installer la charge de travail .NET MAUI et dotnet new maui -n HelloMaui
pour créer l’application. Ensuite, définissez la propriété MSBuild PublishAot
sur true
dans le fichier projet.
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
Lorsque vous définissez la propriété requise et exécutez dotnet publish
comme indiqué dans l’exemple suivant, l’application sera déployée à l’aide d’AOT natif.
dotnet publish -f net8.0-ios -c Release -r ios-arm64 /t:Run
Limitations
Toutes les fonctionnalités iOS ne sont pas compatibles avec Native AOT. De même, toutes les bibliothèques couramment utilisées dans iOS ne sont pas compatibles avec NativeAOT. En plus des limitations existantes du déploiement AOT natif, la liste suivante présente certaines des autres limitations lors du ciblage de plateformes similaires à iOS :
- L’utilisation d’AOT natif n’est activée que pendant le déploiement d’applications (
dotnet publish
). - Le débogage de code managé n’est pris en charge qu’avec Mono.
- La compatibilité avec le framework .NET MAUI est limitée.
Compilation AOT pour les applications Android
Pour réduire la taille des applications, les applications XXX.NET et XXX.NET MAUI qui ciblent Android utilisent le mode de compilation profilé en avance sur le temps (AOT) lorsqu'elles sont construites en mode Release. La compilation AOT profilée affecte moins de méthodes que la compilation AOT standard. .NET 8 introduit la propriété <AndroidStripILAfterAOT>
qui vous permet d’opter pour une compilation AOT supplémentaire pour les applications Android afin de réduire encore plus la taille de l’application.
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
</PropertyGroup>
Par défaut, la définition de AndroidStripILAfterAOT
pour true
remplace le paramètre de AndroidEnableProfiledAot
par défaut, ce qui permet (presque) à toutes les méthodes compilées par AOT d’être supprimées. Vous pouvez également utiliser le mode AOT profilé et le dépouillement de l'IL conjointement en définissant explicitement les deux propriétés sur true
:
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>
Applications Windows croisées
Lorsque vous générez des applications qui ciblent Windows sur des plateformes non-Windows, l’exécutable résultant est maintenant mis à jour avec toutes les ressources Win32 spécifiées, par exemple, l’icône d’application, le manifeste, les informations de version.
Auparavant, les applications devaient être créées sur Windows pour disposer de ces ressources. La résolution de cet écart dans la prise en charge des constructions croisées a été une demande populaire, car il s’agissait d’un point de douleur important affectant à la fois la complexité de l’infrastructure et l’utilisation des ressources.