Partager via


Correction des avertissements de découpage

Lorsque vous activez le découpage dans votre application, le Kit de développement logiciel (SDK) .NET effectue une analyse statique pour détecter les modèles de code qui ne sont peut-être pas compatibles avec le découpage. Les avertissements d'élagage signalent des problèmes potentiels susceptibles de causer des changements de comportement ou des plantages après l'élagage.

Une application qui utilise le découpage ne doit pas produire d’avertissements de découpage. S’il existe des avertissements de découpage, testez soigneusement l’application après le découpage pour vous assurer qu’il n’y a aucune modification de comportement.

Cet article fournit des flux de travail pratiques pour traiter les avertissements de découpage. Pour mieux comprendre pourquoi ces avertissements se produisent et comment fonctionne le découpage, consultez Présentation de l’analyse de découpage.

Présentation des catégories d’avertissements

Les avertissements de découpage appartiennent à deux catégories principales :

  • Code incompatible avec le découpage - Marqué avec RequiresUnreferencedCodeAttribute. Le code ne peut fondamentalement pas être analysé (par exemple, le chargement dynamique d'assemblages ou les modèles de réflexion complexes). La méthode est marquée comme incompatible et les appelants reçoivent des avertissements.

  • Code avec exigences : annoté avec DynamicallyAccessedMembersAttribute. La réflexion est utilisée, mais les types sont connus au moment de la compilation. Lorsque les exigences sont satisfaites, le code devient entièrement compatible.

Flux de travail : déterminer l’approche appropriée

Lorsque vous rencontrez un avertissement de découpage, procédez comme suit :

  1. Éliminer la réflexion - Il s’agit toujours de la meilleure option si possible.
  2. Utilisez DynamicallyAccessedMembers : si les types sont connus, rendez le code compatible avec le rognage.
  3. Utilisez RequiresUnreferencedCode : si elle est véritablement dynamique, documentez l’incompatibilité.
  4. Supprimez les avertissements en dernier recours : uniquement si vous êtes certain que le code est sûr.

Approche 1 : Éliminer la réflexion

La meilleure solution consiste à éviter entièrement la réflexion lorsque cela est possible. Cela rend votre code plus rapide et entièrement compatible avec Trim.

Utiliser des génériques au moment de la compilation

Remplacez les opérations de type runtime par les paramètres génériques au moment de la compilation :

// ❌ Before: Uses reflection
void CreateAndProcess(Type type)
{
    var instance = Activator.CreateInstance(type);
    // Process instance...
}

// ✅ After: Uses generics
void CreateAndProcess<T>() where T : new()
{
    var instance = new T();
    // Process instance...
}

Utiliser des générateurs de code source

Le .NET moderne fournit des générateurs sources pour les scénarios de réflexion courants :

Pour plus d’informations, consultez Incompatibilités de découpage connues.

Approche 2 : Rendre le découpage de code compatible avec DynamiqueAccessedMembers

Lorsque la réflexion est nécessaire, mais que les types sont connus au moment de la compilation, utilisez DynamicallyAccessedMembersAttribute cette option pour rendre votre découpage de code compatible.

Étape par étape : Annoter l'utilisation de la réflexion

Prenons cet exemple qui génère un avertissement :

void PrintMethodNames(Type type)
{
    // ⚠️ IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods'
    foreach (var method in type.GetMethods())
    {
        Console.WriteLine(method.Name);
    }
}

Étape 1 : Identifier l’opération de réflexion effectuée

Le code appelle GetMethods(), ce qui nécessite que PublicMethods soit conservé.

Étape 2 : annoter le paramètre

Ajoutez DynamicallyAccessedMembers pour indiquer au découpage ce qui est nécessaire :

void PrintMethodNames(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    // ✅ No warning - trimmer knows to preserve public methods
    foreach (var method in type.GetMethods())
    {
        Console.WriteLine(method.Name);
    }
}

Étape 3 : Vérifier que les appelants répondent aux exigences

Lors de l’appel de cette méthode avec un type connu (typeof), l’exigence est automatiquement satisfaite :

// ✅ OK - DateTime's public methods will be preserved
PrintMethodNames(typeof(DateTime));

Étape par étape : propager les exigences via des chaînes d’appels

Lorsque les types transitent par plusieurs méthodes, vous devez propager les exigences :

void Method1()
{
    Method2<DateTime>();  // ⚠️ Warning: Generic parameter needs annotation
}

void Method2<T>()
{
    Type t = typeof(T);
    Method3(t);  // ⚠️ Warning: Argument doesn't satisfy requirements
}

void Method3(Type type)
{
    var methods = type.GetMethods();  // ⚠️ Warning: Reflection usage
}

Étape 1 : Commencer à l’utilisation de la réflexion

Annoter où la réflexion est réellement utilisée :

void Method3(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    var methods = type.GetMethods();  // ✅ Fixed
}

Étape 2 : Propager vers le haut dans la chaîne d'appels

Effectuez un travail vers l’arrière dans la chaîne d’appels :

void Method2<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
    Type t = typeof(T);
    Method3(t);  // ✅ Fixed - T is annotated
}

Étape 3 : Vérifier sur le site d’appel

void Method1()
{
    Method2<DateTime>();  // ✅ Fixed - DateTime's public methods preserved
}

Pour plus d’informations sur la façon dont les exigences transitent par le code, consultez Présentation de l’analyse de découpage.

Les valeurs communes de DynamicallyAccessedMemberTypes

Choisissez le niveau d’accès minimal nécessaire :

Type de membre Quand utiliser
PublicConstructors Utilisation Activator.CreateInstance() ou GetConstructor()
PublicMethods Utilisation GetMethod() ou GetMethods()
PublicFields Utilisation GetField() ou GetFields()
PublicProperties Utilisation GetProperty() ou GetProperties() (sérialisation)
PublicEvents Utilisation de GetEvent() ou GetEvents()

Avertissement

L’utilisation de DynamicallyAccessedMemberTypes.All conserve tous les membres du type cible et tous les membres de ses types imbriqués (mais pas les dépendances transitives telles que les membres sur le type de retour d’une propriété). Cela augmente considérablement la taille de l’application. Plus important encore, les membres conservés deviennent accessibles, ce qui signifie qu’ils peuvent contenir leur propre code problématique. Par exemple, si un membre conservé appelle une méthode marquée avec RequiresUnreferencedCode, cet avertissement ne peut pas être résolu, car le membre est conservé par l’annotation de réflexion plutôt qu’un appel explicite. Utilisez les types de membres minimum requis pour éviter ces problèmes en cascade.

Approche 3 : Marquer le code comme incompatible avec RequiresUnreferencedCode

Lorsque le code ne peut pas être fondamentalement analysable, utilisez-le RequiresUnreferencedCodeAttribute pour documenter l’incompatibilité.

Quand utiliser RequiresUnreferencedCode

Utilisez cet attribut quand :

  • Les types sont chargés dynamiquement : utilisation GetType() avec des chaînes déterminées par le runtime.
  • Les assemblies sont chargées à l'exécution : utilisation LoadFrom(String).
  • Modèles de réflexion complexes : l’utilisation de la réflexion est trop complexe pour annoter.
  • Génération de code à l'exécution : utilisation System.Reflection.Emit ou le mot clé dynamic.

Étape par étape : Marquer les méthodes incompatibles

Étape 1 : Identifier le code réellement incompatible

Exemple de code qui ne peut pas être adapté pour être compatible avec l'élagage :

void LoadPluginByName(string pluginName)
{
    // Type name comes from runtime input - trimmer cannot know what types are needed
    Type pluginType = Type.GetType(pluginName);
    var plugin = Activator.CreateInstance(pluginType);
    // Use plugin...
}

Étape 2 : Ajouter l’attribut RequiresUnreferencedCode

[RequiresUnreferencedCode("Plugin loading by name is not compatible with trimming. Consider using compile-time plugin registration instead.")]
void LoadPluginByName(string pluginName)
{
    Type pluginType = Type.GetType(pluginName);
    var plugin = Activator.CreateInstance(pluginType);
    // ✅ No warnings inside this method - it's marked as incompatible
}

Étape 3 : les appelants reçoivent des avertissements

void InitializePlugins()
{
    // ⚠️ IL2026: Using member 'LoadPluginByName' which has 'RequiresUnreferencedCodeAttribute'
    // can break functionality when trimming application code. Plugin loading by name is not
    // compatible with trimming. Consider using compile-time plugin registration instead.
    LoadPluginByName("MyPlugin");
}

Écriture de messages d’avertissement effectifs

Un bon RequiresUnreferencedCode message doit :

  • Indiquez quelle fonctionnalité est incompatible : Soyez précis sur ce qui ne fonctionne pas avec le découpage.
  • Proposer des alternatives : Guidez les développeurs vers des solutions compatibles avec l'élagage.
  • Soyez concis : gardez les messages courts et actionnables.
// ❌ Not helpful
[RequiresUnreferencedCode("Uses reflection")]

// ✅ Helpful - explains problem and suggests alternative
[RequiresUnreferencedCode("Dynamic type loading is not compatible with trimming. Use generic type parameters or source generators instead.")]

Pour obtenir des conseils plus longs, ajoutez un Url paramètre :

[RequiresUnreferencedCode(
    "Plugin system is not compatible with trimming. See documentation for alternatives.",
    Url = "https://docs.example.com/plugin-trimming")]

Propagation de RequiresUnreferencedCode

Lorsqu’une méthode appelle une autre méthode marquée avec RequiresUnreferencedCode, vous devez généralement propager l’attribut :

class PluginSystem
{
    // Use a constant for consistent messaging
    const string PluginMessage = "Plugin system is not compatible with trimming. Use compile-time registration instead.";

    [RequiresUnreferencedCode(PluginMessage)]
    private void LoadPluginImplementation(string name)
    {
        // Low-level plugin loading
    }

    [RequiresUnreferencedCode(PluginMessage)]
    public void LoadPlugin(string name)
    {
        LoadPluginImplementation(name);  // ✅ No warning - method is also marked
    }
}

Modèles et solutions courants

Modèle : méthodes de fabrique avec Activator.CreateInstance

// ❌ Before: Produces warning
object CreateInstance(Type type)
{
    return Activator.CreateInstance(type);
}

// ✅ After: Trim-compatible
object CreateInstance(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type)
{
    return Activator.CreateInstance(type);
}

Modèle : Systèmes de plug-in qui chargent des assemblys

// This pattern is fundamentally incompatible with trimming
[RequiresUnreferencedCode("Plugin loading is not compatible with trimming. Consider compile-time plugin registration using source generators.")]
void LoadPluginsFromDirectory(string directory)
{
    foreach (var dll in Directory.GetFiles(directory, "*.dll"))
    {
        Assembly.LoadFrom(dll);
    }
}

Modèle : Conteneurs d’injection de dépendances

// Complex DI containers are often incompatible
class Container
{
    [RequiresUnreferencedCode("Service resolution uses complex reflection. Consider using source-generated DI or registering services explicitly.")]
    public object Resolve(Type serviceType)
    {
        // Complex reflection to resolve dependencies
    }
}

Approche 4 : Supprimer les avertissements en dernier recours

Avertissement

Supprimez uniquement les avertissements de réduction si vous êtes absolument certain que le code est sûr. Des suppressions incorrectes peuvent entraîner des échecs d’exécution après la suppression.

Utilisez UnconditionalSuppressMessageAttribute lorsque vous avez vérifié que le code est sûr pour le découpage, mais que l'outil de découpage ne peut pas le prouver statiquement.

Lorsque la suppression est appropriée

Supprimez les avertissements uniquement lorsque :

  1. Vous avez vérifié manuellement que tout le code requis est conservé (via DynamicDependency ou d’autres mécanismes).
  2. Le chemin du code n’est jamais exécuté dans des scénarios réduits.
  3. Vous avez soigneusement testé l’application optimisée.

Comment supprimer les avertissements

[RequiresUnreferencedCode("Uses reflection")]
void MethodWithReflection() { /* ... */ }

[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
    Justification = "All referenced types are manually preserved via DynamicDependency attributes")]
void CallerMethod()
{
    MethodWithReflection();  // Warning suppressed
}

Importante

N’utilisez pas SuppressMessage ni #pragma warning disable pour les avertissements de limitation. Ces opérations fonctionnent uniquement pour le compilateur, mais ne sont pas conservées dans l’assembly compilé. Le trimmer fonctionne avec des assemblies compilés et n'intercepte pas ces suppressions. Utilisez toujours UnconditionalSuppressMessage.

Réduire l’étendue de suppression

Appliquez des suppressions à la plus petite étendue possible. Extrayez l’appel problématique dans une fonction locale :

void ProcessData()
{
    InitializeData();

    CallReflectionMethod();  // Only this call is suppressed

    ProcessResults();

    [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
        Justification = "Types are preserved via DynamicDependency on ProcessData method")]
    void CallReflectionMethod()
    {
        MethodWithReflection();
    }
}

Cette approche :

  • Indique clairement quel appel spécifique est supprimé.
  • Empêche la suppression accidentelle d’autres avertissements si le code change.
  • Maintient la justification près de l'appel supprimé.

Conseils de dépannage

L'avertissement persiste après l'ajout de DynamicallyAccessedMembers

Vérifiez que vous avez annoté l’ensemble de la chaîne d’appels de l’utilisation de la réflexion à la source du Type:

  1. Recherchez où la réflexion est utilisée (par exemple GetMethods()).
  2. Annotez le paramètre de cette méthode.
  3. Suivez la valeur Type en remontant tous les appels de méthode.
  4. Annotez chaque paramètre, champ ou paramètre de type générique dans la chaîne.

Trop d’avertissements à traiter

  1. Commencez par votre propre code : corrigez d’abord les avertissements dans le code que vous contrôlez.
  2. Utilisez TrimmerSingleWarn pour voir les avertissements individuels des paquets.
  3. Déterminez si le découpage est approprié pour votre application.
  4. Vérifiez les incompatibilités de découpage connues pour les problèmes au niveau de l’infrastructure.

Pas certain de quels DynamicallyAccessedMemberTypes à utiliser

Examinez l’API de réflexion appelée :

  • GetMethod() / GetMethods()PublicMethods
  • GetProperty() / GetProperties()PublicProperties
  • GetField() / GetFields()PublicFields
  • GetConstructor() / Activator.CreateInstance() PublicParameterlessConstructor→ ouPublicConstructors
  • GetEvent() / GetEvents()PublicEvents

Utilisez le type le plus étroit possible pour réduire la taille de l’application.

Étapes suivantes