Notre logiciel a plusieurs classes qui devraient être trouvées dynamiquement via la réflexion. Les classes ont toutes un constructeur avec une signature spécifique via laquelle le code de réflexion instancie les objets.
Cependant, lorsque quelqu'un vérifie si la méthode est référencée (par exemple via Visual studio Code Lens), la référence via la réflexion n'est pas comptée. Les gens peuvent manquer leurs références et supprimer (ou modifier) des méthodes apparemment inutilisées.
Comment marquer/documenter les méthodes destinées à être appelées par réflexion?
Idéalement, la méthode doit être marquée de telle manière que les collègues et Visual Studio/Roslyn et d'autres outils automatisés "voient" que la méthode est destinée à être appelée via la réflexion.
Je connais deux options que nous pouvons utiliser, mais les deux ne sont pas tout à fait satisfaisantes. Étant donné que Visual Studio ne peut pas trouver les références:
MyPlugin
la classe dont le constructeur à invoquer par réflexion. Supposons que le code de réflexion appelant recherche les constructeurs qui prennent un paramètre int
. La documentation suivante fait que la lentille de code montre le constructeur ayant 1 référence:/// <see cref="MyPlugin.MyPlugin(int)"/> is invoked via reflection
Quelles meilleures options existent?
Quelle est la meilleure pratique pour marquer une méthode/un constructeur qui doit être appelé par réflexion?
Une combinaison des solutions suggérées:
<see>
- balise pour augmenter le nombre de références pour le constructeur/méthode.UsedImplicitlyAttribute
[UsedImplicitly]
a précisément la sémantique voulue.Install-Package JetBrains.Annotations
.SupressMessageAttribute
pour le message CA1811: Avoid uncalled private code
.Par exemple:
class MyPlugin
{
/// <remarks>
/// <see cref="MyPlugin.MyPlugin(int)"/> is called via reflection.
/// </remarks>
[JetBrains.Annotations.UsedImplicitly]
public MyPlugin(int arg)
{
throw new NotImplementedException();
}
/// <remarks>
/// <see cref="MyPlugin.MyPlugin(string)"/> is called via reflection.
/// </remarks>
[JetBrains.Annotations.UsedImplicitly]
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
Justification = "Constructor is called via reflection")]
private MyPlugin(string arg)
{
throw new NotImplementedException();
}
}
La solution confère l'utilisation prévue du constructeur aux lecteurs humains et aux 3 systèmes d'analyse de code statique les plus utilisés avec C # et Visual Studio.
L'inconvénient est qu'un commentaire et une ou deux annotations peuvent sembler un peu redondants.
Je n'ai jamais eu ce problème dans un projet .Net, mais j'ai régulièrement le même problème avec les projets Java. Mon approche habituelle consiste à utiliser l'annotation @SuppressWarnings("unused")
en ajoutant un commentaire expliquant pourquoi (documenter la raison de la désactivation des avertissements fait partie de mon style de code standard - chaque fois que le compilateur ne parvient pas à comprendre quelque chose, je suppose qu'il est probable qu'un humain puisse également lutter). Cela a l'avantage de garantir automatiquement les outils d'analyse statique sont conscients que le code n'est pas censé avoir des références directes et donnent une raison détaillée aux lecteurs humains.
L'équivalent C # de Java's @SuppressWarnings
est SuppressMessageAttribute
. Pour les méthodes privées , vous pouvez utiliser le message CA1811: éviter le code privé non appelé ; par exemple.:
class MyPlugin
{
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
Justification = "Constructor is called via reflection")]
private MyPlugin(int arg)
{
throw new NotImplementedException();
}
}
Une alternative à la documentation serait d'avoir des tests unitaires pour s'assurer que les appels de réflexion s'exécutent correctement.
De cette façon, si quelqu'un modifie ou supprime les méthodes, votre processus de génération/test devrait vous alerter que vous avez cassé quelque chose.
Eh bien, sans voir votre code, cela ressemble à ceci serait un bon endroit pour introduire un héritage. Peut-être une méthode virtuelle ou abstraite que le constructeur de ces classes peut appeler? Si vous êtes la méthode que vous essayez de marquer est juste le constructeur, alors vous essayez vraiment de marquer une classe et non une méthode ouais? Quelque chose que j'ai fait dans le passé pour marquer les classes est de créer une interface vide. Ensuite, les outils d'inspection de code et la refactorisation peuvent rechercher des classes qui implémentent l'interface.