Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Cet article explique les concepts fondamentaux de l’analyse de réduction pour vous aider à comprendre pourquoi certains modèles de code produisent des avertissements et comment rendre votre code compatible avec la réduction. La compréhension de ces concepts vous aidera à prendre des décisions éclairées lors du traitement des avertissements de découpage, plutôt que de simplement distribuer les attributs pour faire taire les outils.
Comment l'analyseur de code optimise le code
Le trimmer effectue une analyse statique au moment de la publication pour identifier le code utilisé par votre application. Il commence par des points d’entrée connus (comme votre Main méthode) et suit les chemins de code de votre application.
Ce que la tondeuse peut comprendre
La tondeuse excelle dans l’analyse des modèles de code directement visibles à la compilation.
// The trimmer CAN understand these patterns:
var date = new DateTime();
date.AddDays(1); // Direct method call - trimmer knows AddDays is used
var list = new List<string>();
list.Add("hello"); // Generic method call - trimmer knows List<string>.Add is used
string result = MyUtility.Process("data"); // Direct static method call
Dans ces exemples, le trimmer peut suivre le chemin d'accès du code et marquer DateTime.AddDays, List<string>.Add, et MyUtility.Process comme du code utilisé qui doit être conservé dans l'application finale.
Ce que la tondeuse ne peut pas comprendre
La tondeuse rencontre des difficultés avec les opérations dynamiques où la cible d'une opération n'est pas connue jusqu'à ce que l'opération soit en cours d'exécution.
// The trimmer CANNOT fully understand these patterns:
Type type = Type.GetType(Console.ReadLine()); // Type name from user input
type.GetMethod("SomeMethod"); // Which method? On which type?
object obj = GetSomeObject();
obj.GetType().GetProperties(); // What type will obj be at runtime?
Assembly asm = Assembly.LoadFrom(pluginPath); // What's in this assembly?
Dans ces exemples, la tondeuse ne peut pas savoir :
- Quel type l’utilisateur entrera
- Quel type
GetSomeObject()retourne - Quel code existe dans l’assembly chargé dynamiquement
Il s’agit du problème fondamental que les avertissements de trim abordent.
Le problème de réflexion
La réflexion permet au code d’inspecter et d’appeler dynamiquement des types et des membres au moment de l’exécution. Cela est puissant, mais crée un défi pour l’analyse statique.
Pourquoi la réflexion empêche l'élagage
Prenons cet exemple :
void PrintMethodNames(Type type)
{
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
// Called somewhere in the app
PrintMethodNames(typeof(DateTime));
Du point de vue de la tondeuse :
- On remarque que
type.GetMethods()est appelé. - Il ne sait pas ce qui
typesera (il s’agit d’un paramètre). - Il ne peut pas déterminer quelles méthodes des différents types doivent être préservées.
- Sans guidance, cela pourrait supprimer des méthodes de
DateTime, endommageant le code.
Par conséquent, l'outil d'élagage génère un avertissement sur ce code.
Comprendre DynamicallyAccessedMembers
DynamicallyAccessedMembersAttribute résout le problème de réflexion en créant un contrat explicite entre l’appelant et la méthode appelée.
L’objectif fondamental
DynamicallyAccessedMembers informe le trimmer : « Ce paramètre (ou champ, ou valeur de retour) contiendra un Type dont il faudra conserver des membres spécifiques, car l'accès à ceux-ci se fera par réflexion. »
Exemple concret
Nous allons corriger l’exemple précédent :
void PrintMethodNames(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
// When this is called...
PrintMethodNames(typeof(DateTime));
Maintenant, le découpage comprend :
-
PrintMethodNamesnécessite que son paramètre aitPublicMethodsété conservé. - Le site d’appel passe
typeof(DateTime). - Par conséquent,
DateTimeles méthodes publiques doivent être conservées.
L’attribut crée une exigence qui s'écoule en sens inverse de l'utilisation de la réflexion vers la source de la Type valeur.
C’est un contrat, pas un conseil
Il est essentiel de comprendre : DynamicallyAccessedMembers ce n’est pas seulement de la documentation. La tondeuse assure ce contrat.
Analogie avec les contraintes de type générique
Si vous êtes familiarisé avec les contraintes de type générique, DynamicallyAccessedMembers fonctionne de la même façon. Tout comme les contraintes génériques transitent par votre code :
void Process<T>(T value) where T : IDisposable
{
value.Dispose(); // OK because constraint guarantees IDisposable
}
void CallProcess<T>(T value) where T : IDisposable
{
Process(value); // OK - constraint satisfied
}
void CallProcessBroken<T>(T value)
{
Process(value); // ERROR - T doesn't have IDisposable constraint
}
DynamicallyAccessedMembers crée des exigences similaires qui transitent par votre code :
void UseReflection([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
type.GetMethods(); // OK because annotation guarantees methods are preserved
}
void PassType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
UseReflection(type); // OK - requirement satisfied
}
void PassTypeBroken(Type type)
{
UseReflection(type); // WARNING - type doesn't have required annotation
}
Les deux créent des contrats qui doivent être remplis et produisent des erreurs ou des avertissements lorsque le contrat ne peut pas être satisfait.
Application du contrat
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
Type GetTypeForProcessing()
{
return typeof(DateTime); // OK - trimmer will preserve DateTime's public methods
}
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
Type GetTypeFromInput()
{
// WARNING: The trimmer can't verify that the type from GetType()
// will have its public methods preserved
return Type.GetType(Console.ReadLine());
}
Si vous ne pouvez pas remplir le contrat (comme dans le deuxième exemple), vous recevez un avertissement.
Présentation de RequiresUnreferencedCode
Certains modèles de code ne peuvent simplement pas être analysés statiquement. Pour ces cas, utilisez RequiresUnreferencedCodeAttribute.
Quand utiliser RequiresUnreferencedCode
Utilisez l’attribut RequiresUnreferencedCodeAttribute quand :
- Le modèle de réflexion est fondamentalement dynamique : chargement d’assemblys ou de types par noms de chaîne à partir de sources externes.
- La complexité est trop élevée pour annoter : le code qui utilise la réflexion de manière complexe et pilotée par les données.
-
Vous utilisez la génération de code runtime : technologies telles que System.Reflection.Emit ou le
dynamicmot clé.
Exemple :
[RequiresUnreferencedCode("Plugin loading is not compatible with trimming")]
void LoadPlugin(string pluginPath)
{
Assembly pluginAssembly = Assembly.LoadFrom(pluginPath);
// Plugin assemblies aren't known at publish time
// This fundamentally cannot be made trim-compatible
}
Objectif de l’attribut
RequiresUnreferencedCode sert à deux fins :
- Supprime les avertissements à l’intérieur de la méthode : le trimmer n’analyse ni n’avertit l’utilisation de la réflexion.
- Crée des avertissements sur les sites d’appel : tout code appelant cette méthode obtient un avertissement.
Cela « bulle » l’avertissement pour donner aux développeurs une visibilité sur les chemins de code incompatibles.
Écriture de bons messages
Le message doit aider les développeurs à comprendre leurs options :
// ❌ Not helpful
[RequiresUnreferencedCode("Uses reflection")]
// ✅ Helpful - explains what's incompatible and suggests alternatives
[RequiresUnreferencedCode("Plugin loading is not compatible with trimming. Consider using a source generator for known plugins instead")]
Comment les exigences transitent par le code
Comprendre comment les exigences se propagent vous permet de savoir où ajouter des attributs.
Les exigences remontent
Flux des exigences de l’endroit où la réflexion est utilisée jusqu’à l’origine du Type.
void CallChain()
{
// Step 1: Source of the Type value
ProcessData<DateTime>(); // ← Requirement ends here
}
void ProcessData<T>()
{
// Step 2: Type flows through generic parameter
var type = typeof(T);
DisplayInfo(type); // ← Requirement flows back through here
}
void DisplayInfo(Type type)
{
// Step 3: Reflection creates the requirement
type.GetMethods(); // ← Requirement starts here
}
Pour rendre ce découpage compatible, vous devez annoter la chaîne :
void ProcessData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
var type = typeof(T);
DisplayInfo(type);
}
void DisplayInfo(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
type.GetMethods();
}
Maintenant, les flux d'exigences : GetMethods() nécessite PublicMethods → type paramètre nécessite PublicMethods → T générique nécessite PublicMethods → DateTime nécessite que PublicMethods soit conservé.
Flux d’exigences via le stockage
Les exigences sont aussi transmises par les champs et les propriétés :
class TypeHolder
{
// This field will hold Types that need PublicMethods preserved
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
private Type _typeToProcess;
public void SetType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
_typeToProcess = typeof(T); // OK - requirement satisfied
}
public void Process()
{
_typeToProcess.GetMethods(); // OK - field is annotated
}
}
Choix de l’approche appropriée
Lorsque vous rencontrez du code qui a besoin de réflexion, suivez cet arbre de décision :
1. Pouvez-vous éviter la réflexion ?
La meilleure solution consiste à éviter la réflexion lorsque cela est possible :
// ❌ Uses reflection
void Process(Type type)
{
var instance = Activator.CreateInstance(type);
}
// ✅ Uses compile-time generics instead
void Process<T>() where T : new()
{
var instance = new T();
}
2. Le type est-il connu au moment de la compilation ?
Si la réflexion est nécessaire, mais que les types sont connus, utilisez DynamicallyAccessedMembers:
// ✅ Trim-compatible
void Serialize<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(T obj)
{
foreach (var prop in typeof(T).GetProperties())
{
// Serialize property
}
}
3. Le modèle est-il fondamentalement dynamique ?
Si les types ne sont pas connus jusqu’à l’exécution, utilisez RequiresUnreferencedCode:
// ✅ Documented as trim-incompatible
[RequiresUnreferencedCode("Dynamic type loading is not compatible with trimming")]
void ProcessTypeByName(string typeName)
{
var type = Type.GetType(typeName);
// Work with type
}
Modèles et solutions courants
Modèle : méthodes de fabrique
// Problem: Creating instances from Type parameter
object CreateInstance(Type type)
{
return Activator.CreateInstance(type);
}
// Solution: Specify constructor requirements
object CreateInstance(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type)
{
return Activator.CreateInstance(type);
}
Modèle : Systèmes de plug-in
// Problem: Loading unknown assemblies at runtime
[RequiresUnreferencedCode("Plugin loading is not trim-compatible. Plugins must be known at compile time.")]
void LoadPlugins(string pluginDirectory)
{
foreach (var file in Directory.GetFiles(pluginDirectory, "*.dll"))
{
Assembly.LoadFrom(file);
}
}
// Better solution: Known plugins with source generation
// Use source generators to create plugin registration code at compile time
Points clés à prendre
- Le découpage utilise l’analyse statique : il ne peut comprendre que les chemins de code visibles au moment de la compilation.
- La réflexion compromet l'analyse statique : l'outil de réduction ne peut pas voir ce que la réflexion accède au moment de l'exécution.
- DynamicallyAccessedMembers crée des contrats : il indique au trimmer ce qui doit être conservé.
-
Les exigences sont descendantes , de l’utilisation de la réflexion à la source de la
Typevaleur. - RequiresUnreferencedCode documents incompatibility : utilisez-le quand le code ne peut pas être analysé.
- Les attributs ne sont pas seulement des indicateurs : le découpage applique des contrats et produit des avertissements lorsqu’ils ne peuvent pas être respectés.
Étapes suivantes
- Corriger les avertissements de découpage - Appliquer ces concepts pour résoudre les avertissements dans votre code
- Préparer les bibliothèques pour le découpage - Rendre vos bibliothèques compatibles avec la suppression
- Référence d’avertissement de découpage - Informations détaillées sur les avertissements spécifiques