Stratégies de migration nullables

Tip

Démarrage d’un nouveau projet ? Les nouveaux projets créés à partir de modèles .NET 6 ou de version ultérieure ont déjà la valeur <Nullable>enable</Nullable> définie. Vous n’avez pas besoin d’une stratégie de migration : passez à résoudre les avertissements nullables.

Gestion d’une base de code existante ? Lisez d’abord les types de référence Nullables pour comprendre les contextes, les annotations et l’état Null. Cet article suppose que vous êtes familiarisé avec ces concepts et prêt à planifier un déploiement.

Lorsque vous activez des types de référence nullables sur un projet volumineux qui a démarré avant l’introduction des types de référence nullable, le compilateur génère plusieurs avertissements à la fois. La migration consiste à séquencer le travail : choisir un contexte par défaut, exposer le fichier d’avertissements par fichier ou section par section et converger pour <Nullable>enable</Nullable> l’ensemble du projet. La séquence appropriée dépend de la façon dont le codebase est actif et du risque que vous pouvez prendre dans une seule passe.

L’état final est le même dans tous les cas : le projet définit <Nullable>enable</Nullable> et ne contient aucune #nullable directive de préprocesseur.

Choisir un contexte par défaut

Le contexte nullable a deux indicateurs indépendants : les annotations (si ? déclare un type de référence nullable) et les avertissements (si le compilateur émet des diagnostics). Définissez-les ensemble en tant que valeur unique <Nullable> :

Valeur par défaut Annotations Avertissements Idéal pour
disable (implicite) désactivé désactivé Bibliothèques stables qui ne feront pas l'objet de nouvelles fonctionnalités lors de cette phase.
enable on on Codebases actifs avec de nouveaux fichiers fréquents. Le nouveau code commence à être choisi.
warnings désactivé on Migration en deux phases : traiter les avertissements d’abord, annoter ensuite.
annotations on désactivé Annotez l’API publique avant de corriger les avertissements internes.

Choisissez la stratégie qui correspond le mieux aux objectifs de la migration de votre projet :

  • Désactivez comme valeur par défaut. Définissez <Nullable>disable</Nullable> et ajoutez #nullable enable en haut de chaque fichier à mesure que vous la migrez. Les fichiers existants restent ignorants de la nullabilité tant que vous ne les modifiez pas. Cette option présente les frictions les plus faibles pour les bibliothèques stables, car le nouveau travail de fonctionnalité est rare.
  • Activez comme valeur par défaut. Définissez <Nullable>enable</Nullable> et ajoutez #nullable disable en haut de chaque fichier que vous n’avez pas encore migré. Chaque nouveau fichier est compatible avec la nullabilité dès le départ, de sorte que le backlog de migration ne peut que diminuer. Ce choix est préférable lorsque le développement est actif.
  • Avertissements comme valeur par défaut. Définissez <Nullable>warnings</Nullable>. Choisissez cette option par défaut pour une migration en deux phases : traitez les avertissements tant que chaque type de référence est encore considéré comme non annoté, puis activez les annotations. Le fractionnement en deux phases permet de concentrer les différences de chaque étape.
  • Annotations comme valeur par défaut. Définissez <Nullable>annotations</Nullable>. Commencez par annoter votre API publique (? pour les membres qui autorisent null) avant de vous attaquer aux avertissements. Le compilateur n’émet pas encore d’avertissements. Vous pouvez donc régler la surface de l’API sans distraction.

Votre fichier projet contrôle la valeur par défaut globale. #nullableLes directives de préprocesseur remplacent cette valeur par défaut pour une région de code :

<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>

Dans les fichiers sources, la directive opte pour une région dans ou hors du paramètre nullable du projet :

#nullable disable
public static class LegacyHelper
{
    // This file is nullable-oblivious. Reference types use the legacy rules.
    public static string GetGreeting(string name) =>
        name == null ? "hello" : $"hello {name}";
}
#nullable restore

#nullable enable
public static class MigratedHelper
{
    // This file is fully migrated. Reference types are non-nullable by default.
    public static string GetGreeting(string? name) =>
        name is null ? "hello" : $"hello {name}";
}
#nullable restore

Migrer un fichier par fichier

La façon la plus prévisible de migrer un projet volumineux consiste à activer des avertissements ou des annotations par fichier. Le modèle est le même, quelle que soit la valeur par défaut que vous choisissez :

  1. Sélectionnez un fichier. Commencez par les types feuilles les plus profonds de votre graphe de dépendances, puis progressez vers l’extérieur. Annoter un type entraîne de nouveaux avertissements dans le code appelant ; travailler de bas en haut limite donc le travail à refaire.
  2. Ajoutez la directive #nullable qui fait adopter au fichier le nouveau comportement. Utilisez #nullable enable si vous voulez les deux drapeaux. Utilisez #nullable enable warnings uniquement pour les avertissements.
  3. Résolvez les avertissements dans le fichier à l’aide des techniques de résolution des avertissements nullables.
  4. Répétez l’opération pour le fichier suivant.
  5. Lorsque chaque fichier du projet a sa directive, supprimez les directives et définissez <Nullable>enable</Nullable> au niveau du projet.

Si votre base de code a déjà <Nullable>enable</Nullable>, vous allez dans la direction opposée. Masquez les avertissements dans les fichiers non migrés jusqu’à ce que vous soyez prêt. Utilisez #nullable disable pour exclure des fichiers, puis supprimez les exceptions une par une.

Migrer en deux phases

Une migration en deux phases sépare les deux types de travail impliquant des types de référence nullables. Vous pouvez séquencer les phases d’une manière ou d’une autre, selon la forme de stabilité qui vous intéresse davantage.

Avertissements d’abord, puis annotations

Privilégiez les avertissements lorsque la correction des bogues latents System.NullReferenceException est prioritaire :

  1. Phase 1 : Avertissements d’adresse. Définissez le projet par défaut sur warnings. Les types de référence restent non sensibles à la nullabilité, donc le système de types ne change pas encore. Le compilateur émet des avertissements partout où votre code existant risque déjà de lever un System.NullReferenceException. Ajoutez des vérifications de nullité, réorganisez le flux d’exécution ou appliquez des attributs jusqu’à ce que le projet soit sans avertissement. Chaque correctif rend le code de production plus résilient même avant l’existence d’annotations.
  2. Phase 2 : Ajouter des annotations. Définissez enable comme projet par défaut. Les types de référence sont désormais non nullables par défaut, et les variables locales var deviennent nullables. Les nouveaux avertissements reflètent les déclarations qui ne correspondent pas à la façon dont les variables sont utilisées. Ajouter ? aux types qui doivent autoriser null. Renforcez les API qui devraient exiger des paramètres non nuls.

Annotations d’abord, puis avertissements

Privilégiez les annotations lorsque la stabilisation de la surface de l’API publique est prioritaire. Cette séquence convient aux bibliothèques : vous pouvez expédier des signatures annotées afin que les consommateurs voient les bons contrats, puis fermer les avertissements internes selon votre propre planification.

  1. Phase 1 : Ajouter des annotations. Définissez le projet par défaut sur annotations. Les types de référence deviennent non nullables par défaut, mais le compilateur n’émet pas d’avertissements, de sorte que le bruit reste hors de votre chemin. Parcourez l’API publique et ajoutez ? à chaque membre pouvant légitimement retourner ou accepter null. Renforcez les signatures qui ne devraient pas l’être. Étant donné que les avertissements sont désactivés, vous pouvez régler la forme d’API dans les validations prioritaires sans annuler l’implémentation en même temps.
  2. Phase 2 : Avertissements d’adresse. Définissez enable comme projet par défaut. Les annotations que vous avez ajoutées à la phase 1 alimentent désormais l’analyse de l’état null. Par conséquent, les avertissements émis par le compilateur sont de meilleure qualité dès le début : chacun pointe au niveau du code dont le comportement ne correspond pas au contrat que vous avez déjà publié. Résolvez-les à l’aide des techniques décrites dans Résoudre les avertissements de types nullable.

Choix entre les ordres

Chaque classement sépare les phases en différences plus petites et plus révisables. Une phase change uniquement le comportement, et les autres changent uniquement les types. L’inconvénient est que vous visitez chaque fichier deux fois. Pour le code mature et stable où chaque modification comporte des risques, les deux passes en valent généralement la peine. Sélectionnez d’abord les avertissements lorsque vous souhaitez renforcer le code en cours d’exécution. Choisissez d’abord les annotations lorsque vous souhaitez publier un contrat stable.

Le code généré est exclu

Le compilateur traite les fichiers marqués comme générés comme si le contexte nullable a été désactivé, quel que soit le paramètre du projet. Un fichier est considéré comme généré lorsque l’une des conditions suivantes est remplie :

  • Une règle .editorconfig définit generated_code = true pour le fichier.
  • Le premier commentaire du fichier contient <auto-generated> ou <auto-generated/>.
  • Le nom du fichier commence par TemporaryGeneratedFile_.
  • Le nom de fichier se termine par .designer.cs, .generated.cs, .g.csou .g.i.cs.

Les générateurs qui produisent une sortie compatible avec les types nullable peuvent réactiver cette option en émettant #nullable enable au début du fichier généré.

Lorsque vous avez terminé

Une fois que chaque fichier participe à la valeur par défaut du projet et que l’élément <Nullable>enable</Nullable> est défini :

  • Supprimez chaque #nullable directive de votre source.
  • Supprimez les initialisations null! et default! que vous avez ajoutées uniquement pour supprimer les avertissements lors de la migration. Remplacez-les par une initialisation appropriée ou définissez un type de référence nullable.
  • Vérifiez l’API publique. Chaque membre qui retourne ou accepte null doit être annoté avec ?. Les annotations font partie de votre contrat une fois le package fourni.

Vous êtes maintenant dans le même état que les nouveaux projets : les types de référence nullables font partie du système de types, et tous les nouveaux avertissements reflètent une incompatibilité réelle entre les déclarations et le code. Utilisez résoudre les avertissements nullables pour les traiter à mesure qu’ils arrivent.