Vous pouvez télécharger .NET 8 Preview 4 pour Linux, macOS et Windows.
- Installateurs et binaires
- Images de conteneurs
- Notes de version
- Problèmes connus
- Suivi des problèmes sur GitHub
Microsoft Build 2023 arrive ! L'équipe .NET organisera un certain nombre de sessions, des approfondissements techniques aux questions-réponses avec l'équipe.
Découvrez les nouveautés d'ASP.NET Core et EF Core dans la version Preview 4. Restez au courant de ce qui est nouveau et à venir dans What's New in .NET 8. Il sera mis à jour tout au long de la version.
Enfin, .NET 8 a été testé avec la version 17.7 Preview 1. Il est recommandé d'utiliser les builds du canal de prévisualisation si vous souhaitez essayer .NET 8 avec la famille de produits Visual Studio. La prise en charge de .NET 8 par Visual Studio pour Mac n'est pas encore disponible. Si vous vous en tenez au canal stable, découvrez les dernières fonctionnalités et améliorations de la version 17.6 de Visual Studio.
Jetons maintenant un coup d'œil à quelques nouvelles fonctionnalités de .NET 8.
MSBuild : nouveau terminal moderne de sortie
https://github.com/dotnet/msbuild/issues/8370
Les utilisateurs nous font souvent remarquer que la sortie par défaut de MSBuild (connue en interne sous le nom de "console logger" est difficile à analyser. Elle est assez statique, est souvent un mur de texte, et émet des erreurs lorsqu'elles sont déclenchées pendant la construction au lieu de les montrer logiquement comme faisant partie du projet en cours de construction. Nous pensons qu'il s'agit là d'excellents retours, et nous sommes heureux de présenter notre première itération d'une nouvelle version, plus moderne, de la journalisation des sorties de MSBuild. Nous l'avons appelé Terminal Logger, et il a quelques objectifs principaux :
- Regrouper logiquement les erreurs avec le projet auquel elles appartiennent
- Présenter les projets/constructions de manière à ce que les utilisateurs pensent à la construction (en particulier les projets multi-cibles)
- Mieux différencier les TargetFrameworks pour lesquels un projet est construit
- Continuer à fournir des informations d'un coup d'œil sur les résultats d'un projet
- Fournir des informations sur ce que la compilation est en train de faire au cours d'une compilation.
Voici à quoi cela ressemble.
La nouvelle sortie peut être activée en utilisant /tl, optionnellement avec l'une des options suivantes :
- auto - la valeur par défaut, qui vérifie si le terminal est capable d'utiliser les nouvelles fonctionnalités et n'utilise pas une sortie standard redirigée avant d'activer le nouveau logger,
- on - annule la détection de l'environnement mentionnée ci-dessus et force l'utilisation du nouveau logger
- off - annule la détection de l'environnement mentionnée ci-dessus et force l'utilisation de l'ancien enregistreur de la console.
Une fois activé, le nouveau logger affiche la phase de restauration, suivie de la phase de construction. Durant chaque phase, les projets en cours de construction sont affichés en bas du terminal, et chaque projet en cours de construction vous indique la cible MSBuild en cours de construction, ainsi que le temps passé sur cette cible. Nous espérons que ces informations rendront les constructions moins mystérieuses pour les utilisateurs, et qu'elles leur donneront un point de départ pour effectuer des recherches lorsqu'ils voudront en savoir plus sur la construction ! Au fur et à mesure que les projets sont construits, une section "construction terminée" est écrite pour chaque construction.
- Le nom du projet construit
- Le Framework cible (s'il est multi-cibles !)
- Le statut de cette compilation
- Le résultat principal de cette compilation (avec un lien hypertexte pour un accès rapide)
- Et enfin, tous les diagnostics générés par la compilation pour ce projet.
Il n'y avait pas de diagnostic pour cet exemple - regardons une autre compilation du même projet où une coquille a été introduite.
Ici, vous pouvez clairement voir le projet et l'erreur typographique décrits.
Nous pensons que cette présentation correspond à la modernité de .NET et qu'elle utilise les capacités des terminaux modernes pour informer les utilisateurs sur leurs créations. Nous espérons que vous l'essaierez et que vous nous ferez part de vos commentaires sur son fonctionnement, ainsi que des autres informations que vous souhaiteriez y voir figurer. Nous espérons utiliser ce logger comme base pour un nouveau lot d'améliorations UX pour MSBuild - y compris des aspects tels que les rapports de progression et les erreurs structurées à l'avenir ! Au fur et à mesure que vous l'utilisez, merci de nous faire part de vos commentaires via cette enquête, ou via la section Discussions du référentiel MSBuild. Nous sommes impatients d'avoir de vos nouvelles !
SDK : Mise à jour simplifiée du chemin de sortie
https://github.com/dotnet/designs/pull/281
https://github.com/dotnet/sdk/pull/31955
Dans l'aperçu 3, nous avons annoncé la nouvelle présentation simplifiée du chemin de sortie pour les projets SDK .NET et nous vous avons demandé de nous faire part de vos commentaires et de votre expérience de l'utilisation de cette nouvelle présentation. Nous vous en remercions ! Vos commentaires ont donné lieu à de nombreuses discussions et, sur la base de ce que nous avons entendu de la part de tous ceux qui ont testé les changements, nous avons apporté les mises à jour suivantes à la fonctionnalité :
- Le chemin d'accès par défaut à la nouvelle présentation passe de .artifacts à artifacts
- Nous supprimons la possibilité d'utiliser la fonctionnalité à partir de fichiers de projet au lieu de Directory.Build.props.
- Nous facilitons la prise en main de la fonctionnalité en incluant les propriétés requises en tant qu'option dans le modèle buildprops pour dotnet new.
J'aimerais expliquer le processus de réflexion qui nous a conduits à ces changements. Vous avez tous massivement soutenu la suppression du . du nom du dossier, principalement pour des raisons de visibilité sur les systèmes Unix, où le . signifie généralement un fichier ou un dossier "caché". Nous savions donc que nous voulions faire ce changement. Cependant, il y a deux raisons principales pour lesquelles nous ne voulions pas utiliser les artifacts comme chemin racine initialement -
- le support de .gitignore
- les obstacles liés à la globalisation des fichiers du SDK .NET
Nous ne voulions pas que les gens aient soudainement à gérer des changements dans leurs fichiers .gitignore juste pour essayer la fonctionnalité, mais après quelques recherches, nous avons découvert qu'un contributeur entreprenant et tourné vers l'avenir (merci @sayedihashimi !) s'est déjà assuré que les artifacts sont dans tous les modèles communs pour les fichiers .gitignore. Cela signifie que nous n'avons pas eu à nous soucier des utilisateurs qui vérifiaient les binaires de manière inattendue.
Nous ne voulions pas non plus inclure accidentellement les sorties d'artifacts dans les modèles globaux par défaut que le SDK .NET utilise pour trouver les fichiers sources à construire dans un projet. Si nous changions le chemin racine de .artifacts à artifacts et que nous laissions les utilisateurs utiliser les nouvelles fonctionnalités au niveau du fichier de projet, nous devrions également modifier toutes les inclusions par défaut qui rendent les fichiers de projet du SDK si succincts. Cela semblait très propice aux erreurs, et franchement un gouffre d'échec pour l'expérience utilisateur. En conséquence, nous avons resserré les conditions d'utilisation de la fonctionnalité - vous devez maintenant opter pour la fonctionnalité via le fichier Directory.Build.props. Cela a pour effet secondaire de rendre la fonctionnalité plus stable. Avant cette modification, l'emplacement du dossier racine inféré changeait lorsqu'un fichier Directory.Build.props était créé. Maintenant, parce qu'un Directory.Build.props doit exister, l'emplacement du chemin d'accès aux artefacts devrait rester stable.
Pour tester la nouvelle version de la fonctionnalité, nous avons facilité la génération du fichier Directory.Build.props correct : il suffit d'exécuter dotnet new buildprops --use-artifacts et nous générerons tout ce dont vous avez besoin. Le fichier Directory.Build.props généré ressemble à ceci :
Code : | Sélectionner tout |
1 2 3 4 5 6 | <Project> <!-- See https://aka.ms/dotnet/msbuild/customize for more details on customizing your build --> <PropertyGroup> <ArtifactsPath>$(MSBuildThisFileDirectory)artifacts</ArtifactsPath> </PropertyGroup> </Project> |
Template Engine : expérience sécurisée avec les paquets de Nuget.org
Dans .NET 8, nous intégrons plusieurs fonctionnalités liées à la sécurité de NuGet.org dans le moteur de modèles, en particulier dans l'expérience dotnet new.
Améliorations
- Empêche le téléchargement de paquets à partir de flux http:\Nmais autorise les surcharges avec l'option --force.
L'équipe NuGet a mis en place un plan d'action pour passer progressivement à une approche sécurisée par défaut. Vous pouvez en savoir plus sur leur plan, et les délais impliqués, dans l'article du blog HTTPS Everywhere. Pour soutenir cet objectif, nous allons commencer à générer des erreurs par défaut lorsqu'une source non-HTTPS est utilisée. Cela peut être remplacé par --force pour la version 8 de .NET, mais le plan actuel est de supprimer ce drapeau pour la version 9 de .NET, en accord avec le calendrier de HTTPS Everywhere.
- Notifier un client si un paquet de modèles a des vulnérabilités lors des vérifications install/update/outdated, et exiger --force pour installer les versions vulnérables.
- Ajouter des données aux commandes de recherche et de désinstallation qui indiquent si un modèle est installé à partir d'un paquetage dont le préfixe est réservé dans NuGet.org.
- Ajout d'informations sur le propriétaire du paquetage du modèle. La propriété est vérifiée par le portail Nuget et peut être considérée comme une caractéristique digne de confiance.
NuGet : vérification des paquets signés sous Linux
À partir du SDK .NET 8 Preview 4, NuGet vérifie par défaut les paquets signés sous Linux. La vérification reste activée sous Windows et désactivée sous macOS.
Pour la plupart des utilisateurs de Linux, la vérification devrait fonctionner de manière transparente. Toutefois, les utilisateurs disposant d'un paquet de certificats racine situé dans /etc/pki/ca-trust/extracted/pem/objsign-ca-bundle.pem peuvent constater des échecs de confiance accompagnés de NU3042.
Les utilisateurs peuvent refuser la vérification en définissant la variable d'environnement DOTNET_NUGET_SIGNATURE_VERIFICATION sur false.
NuGet : Audit des dépendances des paquets pour les vulnérabilités de sécurité
https://github.com/NuGet/Home/issues/8087
https://github.com/NuGet/Home/pull/12310
dotnet restore produira un rapport sur les vulnérabilités de sécurité avec le nom du paquetage affecté, la gravité de la vulnérabilité et un lien vers l'avis pour plus de détails lorsque vous optez pour l'audit de sécurité NuGet.
Activation de l'audit de sécurité
À tout moment, si vous souhaitez recevoir des rapports d'audit de sécurité, vous pouvez opter pour l'expérience en définissant la propriété MSBuild suivante dans un fichier .csproj ou MSBuild en cours d'évaluation dans le cadre de votre projet :
Code : | Sélectionner tout |
<NuGetAudit>true</NuGetAudit>
En outre, assurez-vous que le registre central de NuGet.org est défini comme l'une de vos sources de paquets afin de récupérer l'ensemble de données sur les vulnérabilités connues :
Code : | Sélectionner tout |
1 2 3 | <packageSources> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> </packageSources> |
dotnet add package
Lorsque vous essayez d'ajouter un paquetage qui a une vulnérabilité connue, dotnet restore sera exécuté implicitement et vous le fera savoir par un avertissement.
dotnet restore
Lorsque vous restaurez vos paquets via dotnet restore, vous verrez des avertissements pour chaque paquet et avis affecté.
Codes d'avertissement
Définition d'un niveau d'audit de sécurité
Vous pouvez définir la propriété MSBuild <NuGetAuditLevel> au niveau souhaité pour lequel l'audit échouera. Les valeurs possibles sont low, moderate, high et critical. Par exemple, si vous ne voulez voir que les avis moderate, high et critical, vous pouvez définir ce qui suit :
Code : | Sélectionner tout |
<NuGetAuditLevel>moderate</NuGetAuditLevel>
Bibliothèques : Améliorations de l'UTF8
Avec .NET 8 Preview 4, nous avons introduit la nouvelle interface IUtf8SpanFormattable, qui, comme sa cousine ISpanFormattable, peut être mise en œuvre sur un type pour permettre l'écriture d'une représentation de type chaîne de caractères de ce type vers une étendue de destination. Alors que ISpanFormattable cible UTF16 et Span<char>, IUtf8SpanFormattable cible UTF8 et Span<byte>. Il a également été mis en œuvre pour tous les types primitifs (et d'autres), avec exactement la même logique partagée (grâce à des interfaces abstraites statiques), qu'il s'agisse de string, de Span<char> ou de Span<byte>, ce qui signifie qu'il prend totalement en charge tous les formats (y compris le spécificateur binaire "B" qui est également nouveau dans .NET 8 Preview 4) et toutes les cultures. Cela signifie que vous pouvez désormais formater 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.
En outre, les nouvelles méthodes Utf8.TryWrite fournissent désormais une contrepartie basée sur UTF8 aux méthodes MemoryExtensions.TryWrite UTF16 existantes. Ces méthodes s'appuient sur la prise en charge du gestionnaire de chaînes interpolées introduite dans .NET 6 et C# 10, de sorte que vous pouvez utiliser la syntaxe des chaînes interpolées pour formater une expression complexe directement dans une plage d'octets UTF8, par ex.
Code : | Sélectionner tout |
1 2 | 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, qui, avec son homologue Encoding.TryGetChars, prend en charge l'encodage/décodage dans un span de destination tant que le span est suffisamment long pour contenir l'état résultant, et renvoie false au lieu de lancer une exception si ce n'est pas le cas.
Nous nous attendons à ce que d'autres améliorations de l'UTF8, y compris, mais sans s'y limiter, des améliorations de la performance de cette fonctionnalité, apparaissent dans les prochains aperçus de .NET 8.
Introduction de l'abstraction de temps
L'introduction de la classe abstraite TimeProvider ajoute l'abstraction de temps, qui permet de simuler le temps dans les scénarios de test. Cette fonctionnalité est également prise en charge par d'autres fonctions qui s'appuient sur la progression temporelle, telles que Task.Delay et Task.Async. Cela signifie que même les opérations de Task peuvent être facilement simulées à l'aide de l'abstraction de temps. L'abstraction prend en charge les opérations temporelles essentielles telles que la récupération de l'heure locale et de l'heure UTC, l'obtention d'un horodatage pour la mesure des performances et la création de temporisateurs.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | public abstract class TimeProvider { public static TimeProvider System { get; } protected TimeProvider() public virtual DateTimeOffset GetUtcNow() public DateTimeOffset GetLocalNow() public virtual TimeZoneInfo LocalTimeZone { get; } public virtual long TimestampFrequency { get; } public virtual long GetTimestamp() public TimeSpan GetElapsedTime(long startingTimestamp) public TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) public virtual ITimer CreateTimer(TimerCallback callback, object? state,TimeSpan dueTime, TimeSpan period) } public interface ITimer : IDisposable, IAsyncDisposable { bool Change(TimeSpan dueTime, TimeSpan period); } public partial class CancellationTokenSource : IDisposable { public CancellationTokenSource(TimeSpan delay, TimeProvider timeProvider) } public sealed partial class PeriodicTimer : IDisposable { public PeriodicTimer(TimeSpan period, TimeProvider timeProvider) } public partial class Task : IAsyncResult, IDisposable { public static Task Delay(System.TimeSpan delay, System.TimeProvider timeProvider) public static Task Delay(System.TimeSpan delay, System.TimeProvider timeProvider, System.Threading.CancellationToken cancellationToken) public Task WaitAsync(TimeSpan timeout, TimeProvider timeProvider) public Task WaitAsync(TimeSpan timeout, TimeProvider timeProvider, CancellationToken cancellationToken) } public partial class Task<TResult> : Task { public new Task<TResult> WaitAsync(TimeSpan timeout, TimeProvider timeProvider) public new Task<TResult> WaitAsync(TimeSpan timeout, TimeProvider timeProvider, CancellationToken cancellationToken) } |
En outre, nous avons rendu l'abstraction disponible dans .NET 8.0 et créé une bibliothèque netstandard 2.0 appelée Microsoft.Bcl.TimeProvider. Cela permet d'utiliser l'abstraction sur les versions prises en charge du .NET Framework et des versions antérieures de .NET.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | namespace System.Threading.Tasks { public static class TimeProviderTaskExtensions { public static Task Delay(this TimeProvider timeProvider, TimeSpan delay, CancellationToken cancellationToken = default) public static Task<TResult> WaitAsync<TResult>(this Task<TResult> task, TimeSpan timeout, TimeProvider timeProvider, CancellationToken cancellationToken = default) public static Tasks.Task WaitAsync(this Task task, TimeSpan timeout, TimeProvider timeProvider, CancellationToken cancellationToken = default) public static CancellationTokenSource CreateCancellationTokenSource(this TimeProvider timeProvider, TimeSpan delay) } } |
Exemples d'utilisation
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // Get System time DateTimeOffset utcNow= TimeProvider.System.GetUtcNow(); DateTimeOffset localNow = TimeProvider.System.GetLocalNow(); // Create a time provider that work with a time zone different than the local time zone private class ZonedTimeProvider : TimeProvider { private TimeZoneInfo _zoneInfo; public ZonedTimeProvider(TimeZoneInfo zoneInfo) : base() { _zoneInfo = zoneInfo ?? TimeZoneInfo.Local; } public override TimeZoneInfo LocalTimeZone { get => _zoneInfo; } public static TimeProvider FromLocalTimeZone(TimeZoneInfo zoneInfo) => new ZonedTimeProvider(zoneInfo); } // Create a time using a time provider ITimer timer = timeProvider.CreateTimer(callBack, state, delay, Timeout.InfiniteTimeSpan); // Measure a period using the system time provider long providerTimestamp1 = TimeProvider.System.GetTimestamp(); long providerTimestamp2 = TimeProvider.System.GetTimestamp(); var period = GetElapsedTime(providerTimestamp1, providerTimestamp2); |
https://github.com/dotnet/runtime/issues/36617
System.Runtime.Intrinsics.Vector512 et AVX-512
La prise en charge SIMD est un élément essentiel de .NET depuis de nombreuses années, depuis que nous l'avons introduite pour la première fois dans .NET Framework. Dans .NET Core 3.0, nous avons étendu cette prise en charge pour inclure les API intrinsèques matérielles spécifiques à la plate-forme pour x86/x64. .NET 8 ne fait pas exception à la règle et continue de renforcer son support en introduisant System.Runtime.Intrinsics.Vector512<T> et son accélération sur le matériel x86/x64 avec la prise en charge de l'AVX-512.
AVX-512 lui-même apporte plusieurs fonctionnalités clés dont la Preview 4 ajoute la prise en charge des trois premières. La dernière est encore en cours de développement et nous espérons pouvoir partager plus de détails à une date ultérieure :
- Prise en charge des opérations vectorielles sur 512 bits
- Prise en charge de 16 registres SIMD supplémentaires
- Prise en charge d'instructions supplémentaires disponibles pour les vecteurs 128 bits, 256 bits et 512 bits
- Prise en charge des opérations vectorielles masquées
Si votre matériel prend en charge cette fonctionnalité, Vector512.IsHardwareAccelerated indiquera désormais true. Nous avons également exposé plusieurs classes spécifiques aux plates-formes dans l'espace de noms System.Runtime.Intrinsics.X86, notamment Avx512F (Foundational), Avx512BW (Byte and Word), Avx512CD (Conflict Detection), Avx512DQ (Doubleword and Quadword) et Avx512Vbmi (Vector Byte Manipulation Instructions). Ces instructions suivent le même schéma général que les autres ISA en ce sens qu'elles exposent une propriété IsSupported et une classe imbriquée X64 pour les instructions disponibles uniquement pour les processus 64 bits. De plus, nous avons maintenant une classe imbriquée VL dans chacune d'elles qui expose les extensions Avx512VL (Vector Length) pour le jeu d'instructions correspondant.
En raison des deuxième et troisième caractéristiques clés énumérées ci-dessus, même si vous n'utilisez pas explicitement les instructions spécifiques Vector512 ou Avx512F dans votre code, vous bénéficierez probablement de cette fonctionnalité. En effet, le JIT est en mesure de tirer parti des fonctionnalités implicites lors de l'utilisation de Vector128<T> ou Vector256<T>, ce qui inclut tous les endroits de la BCL qui utilisent des intrinsèques matérielles en interne, comme la plupart des opérations exposées par Span<T> et ReadOnlySpan<T>, la plupart des API mathématiques exposées pour les types primitifs, et bien d'autres encore.
Améliorations de l'AOT natif
Nous avons mis à jour le modèle de console par défaut et ajouté le support d'AOT dès le départ. Il est maintenant possible d'invoquer dotnet new console --aot pour créer un projet configuré pour la compilation AOT. La configuration du projet ajoutée par --aot a trois effets :
- La publication du projet avec, par exemple, dotnet publish ou Visual Studio générera un exécutable autonome natif avec AOT natif.
- Cela activera les analyseurs de compatibilité basés sur Roslyn pour le découpage, l'AOT et le fichier unique qui marqueront les parties potentiellement problématiques de votre projet (s'il y en a) dans l'éditeur de votre choix.
- Il permettra l'émulation de l'AOT lors du débogage, de sorte que lorsque vous déboguez votre projet sans compilation de l'AOT, vous obtenez une expérience similaire à celle de l'AOT. Cela permet de s'assurer que, par exemple, l'utilisation de Reflection.Emit dans un package NuGet qui n'a pas été annoté pour AOT (et qui a donc été manqué par l'analyseur de compatibilité) ne vous surprendra pas lorsque vous essayez de publier le projet avec AOT pour la première fois.
Nous continuons également à améliorer les fondamentaux tels que le débit d'exécution, l'utilisation de la mémoire et la taille sur disque avec Native AOT. Dans l'aperçu 4, nous ajoutons un moyen de communiquer une préférence d'optimisation telle que la vitesse ou la taille. Les paramètres par défaut tentent de trouver le bon équilibre entre ces deux options, mais nous introduisons maintenant un moyen de spécifier la façon de faire les compromis.
Par exemple, l'optimisation du résultat de dotnet new console --aot pour la taille sur Windows x64 permet de réaliser les économies suivantes dans l'aperçu 4 :
Il s'agit de la taille d'une application entièrement autonome qui comprend le moteur d'exécution (y compris le GC) et toutes les bibliothèques de classes nécessaires.
Dans l'aperçu 4, nous avons observé que l'optimisation de la vitesse permet d'améliorer le débit de 2 à 3 % pour les charges de travail réelles.
Prise en charge de la version de la distribution Linux
Nous avons annoncé précédemment que nous mettions à jour les versions de distro Linux prises en charge pour .NET 8. Ces changements sont inclus dans l'aperçu 4, en particulier la version de la glibc ciblée par .NET 8.
.NET 8 est conçu pour Ubuntu 16.04, pour toutes les architectures. C'est principalement important pour définir la version minimale de la glibc pour .NET 8. .NET 8 ne démarrera pas sur les versions de distro qui incluent une glibc plus ancienne, comme Ubuntu 14.04 ou Red Hat Enterprise Linux 7.
Nous sommes également en train de mettre à jour la version Linux de .NET 8 pour utiliser clang 16. Nous prévoyons d'inclure cette modification dans l'aperçu 5. Nous ne ferons pas d'annonce séparée pour cette modification.
Il n'y a pas d'autres changements significatifs. Nous continuerons à prendre en charge .NET sous Linux sur les architectures Arm32, Arm64 et x64.
System.Text.Json : Remplissage des membres en lecture seule
À partir de .NET 8 Preview 4, System.Text.Json introduit la possibilité de désérialiser sur des propriétés ou des champs en lecture seule.
Nous avons également introduit une option qui permet aux développeurs de l'activer pour toutes les propriétés qui sont capables de remplir - par exemple, les convertisseurs personnalisés peuvent ne pas être compatibles avec cette fonctionnalité :
Code : | Sélectionner tout |
1 2 3 4 | JsonSerializerOptions options = new() { PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate }; |
Par exemple, pour activer le remplissage pour toutes les propriétés d'une classe spécifique :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | using System.Text.Json; using System.Text.Json.Serialization; JsonSerializerOptions options = new() { WriteIndented = true, // Instead of granular control we could also enable this globally like this: // PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate }; CustomerInfo customer = JsonSerializer.Deserialize<CustomerInfo>("""{"Person":{"Name":"John"},"Company":{"Name":"John and Son"}}""", options)!; Console.WriteLine(JsonSerializer.Serialize(customer, options)); class PersonInfo { // there is nothing here to be populated since string cannot be re-used public required string Name { get; set; } public string? Title { get; set; } } class CompanyInfo { public required string Name { get; set; } public string? Address { get; set; } public string? PhoneNumber { get; set; } public string? Email { get; set; } } // notes: // - attribute does not apply to the `CustomerInfo` class itself: i.e. properties of type `CustomerInfo` wouldn't be auto-populated // - automatic rules like these can be implemented with contract customization // - attribute do apply to `Person` and `Company` properties // - attribute can also be placed on individual properties [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] class CustomerInfo { private const string NA = "N/A"; // note how neither of these have setters public PersonInfo Person { get; } = new PersonInfo() { Name = "Anonymous", Title = "Software Developer" }; public CompanyInfo Company { get; } = new CompanyInfo() { Name = NA, Address = NA, PhoneNumber = NA, Email = NA }; } |
Le résultat ci-dessus est identique à celui que l'on obtiendrait avec l'option globale :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | { "Person": { "Name": "John", "Title": "Software Developer" }, "Company": { "Name": "John and Son", "Address": "N/A", "PhoneNumber": "N/A", "Email": "N/A" } } |
à titre de comparaison, nous aurions vu notre entrée, mais comme il n'y avait pas de propriété définissable Person ou Company à désérialiser, nous aurions ignoré complètement l'entrée et la sortie n'aurait montré que les valeurs par défaut :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | { "Person": { "Name": "Anonymous", "Title": "Software Developer" }, "Company": { "Name": "N/A", "Address": "N/A", "PhoneNumber": "N/A", "Email": "N/A" } } |
Autres notes concernant le remplissage des membres en lecture seule
- Pour plus d'informations, voir le problème original de la conception : https://github.com/dotnet/runtime/issues/78556
- Les structures peuvent également être alimentées, mais l'alimentation se fait en créant d'abord une copie, puis en la réinitialisant à la propriété, et ces propriétés nécessitent donc également des fixateurs (setters).
- Le peuplement des collections se fait de manière additive - la collection existante avec tout son contenu est traitée comme l'objet d'origine et tous les éléments existants sont donc préservés - ce comportement peut être modifié par la personnalisation du contrat et/ou les rappels de désérialisation.
Améliorations de System.Text.Json
JsonSerializer.IsReflectionEnabledByDefault
https://github.com/dotnet/runtime/pull/83844
La classe JsonSerializer expose un certain nombre de méthodes de sérialisation et de désérialisation qui acceptent un paramètre optionnel JsonSerializerOptions. Si elles ne sont pas spécifiées, ces méthodes utiliseront par défaut le sérialiseur basé sur la réflexion. Dans le contexte des applications AOT natives, ce défaut peut créer des problèmes en ce qui concerne la taille de l'application : même si l'utilisateur prend soin de passer une valeur JsonSerializerOptions générée par la source, il en résultera toujours que les composants de réflexion seront enracinés par l'outil de découpage.
System.Text.Json est désormais livré avec le commutateur de fonctionnalité System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault qui contrôle le comportement par défaut des méthodes JsonSerializer. Le fait de définir le commutateur sur false au moment de la publication permet désormais d'éviter l'enracinement accidentel des composants de réflexion. Il convient de noter qu'avec l'interrupteur désactivé, ce code
Code : | Sélectionner tout |
JsonSerializer.Serialize(new { Value = 42 });
En outre, la valeur du commutateur de fonctionnalité est reflétée dans la propriété JsonSerializer.IsReflectionEnabledByDefault qui est traitée comme une constante de temps de liaison. Les auteurs de bibliothèques construites au-dessus de System.Text.Json peuvent s'appuyer sur cette propriété pour configurer leurs valeurs par défaut sans enraciner accidentellement des composants de réflexion :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | static JsonSerializerOptions GetDefaultOptions() { if (JsonSerializer.IsReflectionEnabledByDefault) { // This branch has a dependency on DefaultJsonTypeInfo // but will get trimmed away by the linker if the feature switch is disabled. return new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver(), PropertyNamingPolicy = JsonNamingPolicy.KebabCase, } } return new() { PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower } ; } |
JsonSerializerOptions.TypeInfoResolverChain
https://github.com/dotnet/runtime/issues/83095
Lorsqu'elle a été livrée en .NET 7, la fonction de personnalisation des contrats a ajouté la prise en charge du chaînage des générateurs de sources au moyen de la méthode JsonTypeInfoResolver.Combine :
Code : | Sélectionner tout |
1 2 3 4 | var options = new JsonSerializerOptions { TypeInfoResolver = JsonTypeInfoResolver.Combine(ContextA.Default, ContextB.Default, ContextC.Default); }; |
- Elle nécessite la spécification de tous les composants chaînés dans un site d'appel - les résolveurs ne peuvent pas être ajoutés à la chaîne après coup.
- L'implémentation du chaînage étant abstraite derrière l'implémentation d'un résolveur IJsonTypeInfoResolver, il n'existe aucun moyen pour les utilisateurs d'introspecter la chaîne ou d'en supprimer des composants.
La classe JsonSerializerOptions comprend désormais une propriété TypeInfoResolverChain qui est complémentaire de TypeInfoResolver :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 | namespace System.Text.Json; public partial class JsonSerializerOptions { public IJsonTypeInfoResolver? TypeInfoResolver { get; set; } public IList<IJsonTypeInfoResolver> TypeInfoResolverChain { get; } } |
L'instance d'options telle que définie dans l'exemple original peut maintenant être manipulée comme suit :
Code : | Sélectionner tout |
1 2 3 | options.TypeInfoResolverChain.Count; // 3 options.TypeInfoResolverChain.RemoveAt(0); options.TypeInfoResolverChain.Count; // 2 |
Obsolescence de JsonSerializerOptions.AddContext
https://github.com/dotnet/runtime/issues/83280
La propriété JsonSerializerOptions.AddContext a été remplacée par les propriétés TypeInfoResolver et TypeInfoResolverChain, elle est donc marquée comme obsolète.
Prise en charge des types unspeakable
https://github.com/dotnet/runtime/issues/82457
Les types générés par le compilateur ou "unspeakable" ont été difficiles à prendre en charge dans les scénarios de génération de source faiblement typée. Dans .NET 7, l'application suivante
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | object value = Test(); JsonSerializer.Serialize(value, MyContext.Default.Options); async IAsyncEnumerable<int> Test() { for (int i = 0; i < 10; i++) { await Task.Delay(1000); yield return i; } } [JsonSerializable(typeof(IAsyncEnumerable |
échoue avec l'erreur
Code : | Sélectionner tout |
Metadata for type 'Program+<<<Main>$>g__Test|0_5>d' was not provided by TypeInfoResolver of type 'MyContext'
En effet, le type Program+<<<Main>$>g__Test|0_5>d généré par le compilateur ne peut pas être explicitement spécifié par le générateur de source.
À partir de l'aperçu 4, System.Text.Json effectue la résolution de l'ancêtre le plus proche lors de l'exécution afin de déterminer le super-type le plus approprié pour sérialiser la valeur (dans ce cas, IAsyncEnumerable<int>).
JsonSerializerOptions.TryGetTypeInfo
https://github.com/dotnet/runtime/pull/84411
L'aperçu 4 inclut désormais une variante Try- de la méthode GetTypeInfo qui renvoie false si aucune métadonnée n'a été trouvée pour le type spécifié.
Codegen
Allocation de registres consécutifs
Dans cette version preview, nous avons introduit une nouvelle fonctionnalité dans notre allocateur de registres appelée allocation de "registres consécutifs". Avant d'entrer dans les détails de ce qu'elle implique et pourquoi elle était nécessaire, examinons d'abord ce qu'est l'allocation de registre et comment elle fonctionne dans RyuJIT.
L'algorithme d'allocation de registre utilisé dans RyuJIT est basé sur une approche "Linear Scan". Il parcourt le programme pour identifier la durée de vie de toutes les variables, appelées "intervalles" dans la littérature, et assigne un seul registre à chaque variable à chaque utilisation. Pour déterminer le meilleur registre à affecter à un point donné, l'algorithme doit identifier les variables qui sont actives à ce point et qui ne se chevauchent pas avec d'autres variables. Il sélectionne ensuite un registre parmi un ensemble de registres libres disponibles, en utilisant une heuristique pour déterminer le meilleur ensemble de registres au point d'affectation. Si aucun registre n'est disponible parce qu'ils sont tous affectés à des intervalles, l'algorithme identifie le meilleur registre qui peut être "déversé" et affecté à cet endroit. Le déversement consiste à stocker la valeur d'un registre sur la pile et à la récupérer plus tard en cas de besoin, ce qui est une opération coûteuse que l'allocateur de registres tente de minimiser.
L'Arm64 possède deux instructions, TBL et TBX, qui sont utilisées pour la recherche de vecteur de table. Ces instructions prennent un "tuple" comme l'un de leurs opérandes, qui peut contenir 2, 3 ou 4 entités. Dans le PR# 80297, nous avons ajouté deux ensembles d'API, VectorTableLookup et VectorTableLookupExtension, sous l'espace de noms AdvSimd pour ces instructions. Cependant, ces instructions exigent que toutes les entités du n-uplet soient présentes dans des registres consécutifs. Pour mieux comprendre cette exigence, prenons un exemple.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | public static Vector128<byte> Test(float f) { var a = Produce1(); var b = Produce2(); var c = a + b; var d = c + a; var e = d + b; d = AdvSimd.Arm64.VectorTableLookup((d, e, e, b), c); } |
Voici le code généré pour la méthode.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | movz x0, #0xD1FFAB1E // code for helloworld:Produce1():System.Runtime.Intrinsics.Vector128`1[ubyte] movk x0, #0xD1FFAB1E LSL #16 movk x0, #0xD1FFAB1E LSL #32 ldr x0, [x0] blr x0 str q0, [fp, #0x20] // [V01 loc0] movz x0, #0xD1FFAB1E // code for helloworld:Produce2():System.Runtime.Intrinsics.Vector128`1[ubyte] movk x0, #0xD1FFAB1E LSL #16 movk x0, #0xD1FFAB1E LSL #32 ldr x0, [x0] blr x0 ldr q16, [fp, #0x20] // [V01 loc0] add v17.16b, v16.16b, v0.16b str q17, [fp, #0x10] // [V03 loc2] add v16.16b, v17.16b, v16.16b add v18.16b, v16.16b, v0.16b mov v17.16b, v18.16b mov v19.16b, v0.16b ldr q20, [fp, #0x10] // [V03 loc2] tbl v16.16b, {v16.16b, v17.16b, v18.16b, v19.16b}, v20.16b add v0.16b, v0.16b, v16.16b |
Dans l'exemple donné, VectorTableLookup() prend un tuple composé de 4 vecteurs d, e, e, et b, qui sont passés dans les registres consécutifs v16 à v19. Même si la deuxième et la troisième valeur sont la même variable e, elles sont toujours passées dans des registres différents v17 et v18. Cela introduit la complexité de trouver non seulement plusieurs registres libres (ou occupés) (2, 3 ou 4) pour les instructions tbl et tbx, mais aussi des registres consécutifs. Afin de répondre à cette nouvelle exigence, notre algorithme a dû être mis à jour à différentes étapes, comme la vérification à l'avance si des registres consécutifs sont libres lors de l'assignation d'un registre à la première entité du tuple, l'assurance que les registres assignés sont consécutifs si les variables ont déjà des registres assignés et qu'ils ne sont pas consécutifs, et l'ajout de scénarios de tests de stress pour gérer les registres alternatifs lorsqu'ils sont disponibles. Dans le PR #85189, @MihaZupan a utilisé le VectorTableLookup dans la méthode IndexOf de ProbabilisticMap et a obtenu une amélioration de 30%.
Optimisation de l'accès aux champs ThreadStatic
L'accès aux champs marqués avec ThreadStatic devait passer par des appels d'aide qui accédaient au stockage local du thread (TLS) du thread et du module en cours avant d'accéder aux données du champ. Dans le PR #82973, nous avons intégré tout ce code et ainsi, la valeur du champ peut être récupérée sans passer par l'aide. Cela permet de multiplier par 10 les performances d'accès aux champs.
Arm64
Nous avons continué à améliorer la qualité du code d'Arm64 et nos amis @SwapnilGaikwad et @a74nh d'Arm ont apporté de bonnes contributions à cette version.
- Dans le PR #84350, les paires de "str wzr" ont été optimisées et remplacées par "str xzr".
- Dans le PR #84135, les optimisations peephole de ldp/stp ont été activées pour les registres SIMD.
- Dans le PR #83458, un chargement a été remplacé par une instruction mov moins chère lorsque cela était possible.
- Dans le PR #79283, les conditions dans la clause if ont été combinées avec des chaînes de comparaison.
- Dans le PR #82031, on a commencé à utiliser cinc au lieu de csel quand c'était possible.
- Dans le PR #84667, combiner 'neg' et 'cmp' en 'cmn'.
- Dans le PR #84605, combiner les opérations cmp et shift en une seule opération cmp.
- Dans le PR #83694, ajouter IsVNNeverNegative (a amélioré tous les arcs, mais a eu un impact important sur ARM64).
Jusqu'à présent, l'optimisation de la paire load/store peephole n'était pas effectuée si l'une des valeurs provenait d'une variable locale. La PR #84399 a corrigé cette limitation et activé l'optimisation peephole de manière générale.
L'opérateur >>> est optimisé pour les intrinsèques ShiftRightLogical sur Arm64 dans le PR#85258.
Vectorisation du code
JIT/NativeAOT peut maintenant dérouler et vectoriser automatiquement diverses opérations mémoire telles que la comparaison, la copie et la mise à zéro avec SIMD (y compris les instructions AVX-512 sur x64 !) s'il peut déterminer leurs tailles au moment de la compilation :
- PR#83255 a rendu la mise à zéro de stackalloc 2 à 3 fois plus rapide avec SIMD
- PR#83638, PR#83740 et PR#84530 ont activé l'auto-vectorisation pour diverses opérations de type "copy buffer".
- Le PR#83945 a fait de même pour les comparaisons, y compris SequenceEqual et StartsWith pour tous les types de primitives. Un bon exemple d'un motif que le JIT peut maintenant vectoriser automatiquement est l'extrait suivant :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | bool CopyFirst50Items(ReadOnlySpan<int> src, Span<int> dst) => src.Slice(0, 50).TryCopyTo(dst); ```csharp ```asm ; Method CopyFirst50Items push rbp vzeroupper mov rbp, rsp cmp edx, 50 ;; src.Length >= 50 ? jb SHORT G_M1291_IG05 xor eax, eax cmp r8d, 50 ;; dst.Length >= 50 ? jb SHORT G_M1291_IG04 vmovdqu zmm0, zmmword ptr [rsi] vmovdqu zmm1, zmmword ptr [rsi+40H] vmovdqu zmm2, zmmword ptr [rsi+80H] vmovdqu xmm3, xmmword ptr [rsi+B8H] vmovdqu zmmword ptr [rcx], zmm0 vmovdqu zmmword ptr [rcx+40H], zmm1 vmovdqu zmmword ptr [rcx+80H], zmm2 vmovdqu xmmword ptr [rcx+B8H], xmm3 mov eax, 1 G_M1291_IG04: pop rbp ret G_M1291_IG05: call [System.ThrowHelper:ThrowArgumentOutOfRangeException()] int3 ; Total bytes of code: 96 |
Ici, le JIT a utilisé 3 registres ZMM (AVX-512) pour effectuer une opération de type memmove en ligne (même si src et dst se chevauchent). Un codegen similaire sera généré pour les données constantes à la compilation, par exemple les littéraux utf8 :
Code : | Sélectionner tout |
1 2 | bool WriteHeader(Span<int> dst) => "text/html"u8.CopyTo(dst); bool StartsWithHeader(Span<int> dst) => dst.StartsWith("text/html"u8); |
Optimisations générales
- PR#83911 Les initialisations statiques sont maintenant moins coûteuses dans NativeAOT.
- PR#84213 et PR#84231 : amélioration de l'élimination des contrôles pour les motifs arr[arr.Length - cns] et arr[index % arr.Length].
- L'optimisation de la substitution vers l'avant est activée pour plus de cas tels que les petits types, PR#83969.
- Amélioration de certains cas de déversements lors de l'allocation des registres avec PR#85251.
- PR#84427 a amélioré l'extensibilité de l'instrumentation PGO.
- Nous avons continué à améliorer les capacités d'optimisation des boucles JIT. Dans l'aperçu 4, nous avons amélioré le calcul des ensembles d'accessibilité, PR#84204.
Résumé
L'aperçu 4 de .NET 8 contient de nouvelles fonctionnalités et améliorations passionnantes qui n'auraient pas été possibles sans le travail acharné et le dévouement d'une équipe diversifiée d'ingénieurs chez Microsoft et d'une communauté open source passionnée. Nous tenons à remercier sincèrement tous ceux qui ont contribué à .NET 8 jusqu'à présent, que ce soit par des contributions au code, des rapports de bogues ou des commentaires.
Vos contributions ont joué un rôle déterminant dans la réalisation des avant-premières de .NET 8, et nous sommes impatients de continuer à travailler ensemble pour construire un avenir meilleur pour .NET et l'ensemble de la communauté technologique.
Et vous ?
Que pensez-vous des nouveautés apportées par cet aperçu de .NET 8 ?
Voir aussi
Microsoft publie .NET 8 Preview 3, le troisième aperçu de la dernière version du framework, et inclut plusieurs changements ainsi que de nombreuses améliorations de performance
Microsoft publie .NET 8 Preview 2, le second aperçu de la dernière version du framework, et apporte plusieurs nouvelles fonctionnalités dans les bibliothèques
Microsoft publie .NET 8 Preview 1, le premier aperçu de la nouvelle version du framework, et ajoute plusieurs nouveautés dont l'extension de Native AOT à plus de scénarios et le support pour Linux