Best Practice zum Markieren einer Methode, die über Reflexion aufgerufen wird?

11

Unsere Software verfügt über mehrere Klassen, die dynamisch über Reflexion gefunden werden sollten. Die Klassen haben alle einen Konstruktor mit einer bestimmten Signatur, über die der Reflektionscode Objekte instanziiert.
Wenn jedoch jemand prüft, ob auf die Methode verwiesen wird (z. B. über Visual Studio Code Lens), wird die Referenz über Reflection nicht gezählt. Menschen können ihre Referenzen verpassen und scheinbar nicht verwendete Methoden entfernen (oder ändern).

Wie sollen Methoden markiert / dokumentiert werden, die durch Reflexion aufgerufen werden sollen?

Idealerweise sollte die Methode so gekennzeichnet sein, dass sowohl Kollegen als auch Visual Studio / Roslyn und andere automatisierte Tools „sehen“, dass die Methode über Reflektion aufgerufen werden soll.

Ich kenne zwei Optionen, die wir verwenden können, aber beide sind nicht ganz zufriedenstellend. Da Visual Studio die Referenzen nicht finden kann:

  • Verwenden Sie ein benutzerdefiniertes Attribut und markieren Sie den Konstruktor mit diesem Attribut.
    • Ein Problem besteht darin, dass Attributeigenschaften keine Methodenreferenz sein können. Daher zeigt der Konstruktor weiterhin 0 Referenzen an.
    • Kollegen, die mit dem benutzerdefinierten Attribut nicht vertraut sind, werden es wahrscheinlich ignorieren.
    • Ein Vorteil meines aktuellen Ansatzes ist, dass der Reflexionsteil das Attribut verwenden kann, um den Konstruktor zu finden, den er aufrufen soll.
  • Verwenden Sie Kommentare, um zu dokumentieren, dass eine Methode / ein Konstruktor über Reflektion aufgerufen werden soll.
    • Automatisierte Tools ignorieren Kommentare (und Kollegen tun dies möglicherweise auch).
    • XML-Dokumentationskommentare können verwendet werden, damit Visual Studio einen zusätzlichen Verweis auf die Methode / den Konstruktor zählt:
      Sei MyPlugindie Klasse, deren Konstruktor über Reflektion aufgerufen werden soll. Angenommen, der aufrufende Reflection-Code sucht nach Konstruktoren, die einen intParameter annehmen . In der folgenden Dokumentation wird gezeigt, dass das Codeobjektiv den Konstruktor mit 1 Referenz zeigt:
      /// <see cref="MyPlugin.MyPlugin(int)"/> is invoked via reflection

Welche besseren Möglichkeiten gibt es?
Was ist die beste Vorgehensweise zum Markieren einer Methode / eines Konstruktors, die bzw. der über Reflexion aufgerufen werden soll?

Kasper van den Berg
quelle
Nur um klar zu sein, das ist für eine Art Plugin-System, oder?
Whatsisname
2
Sie gehen davon aus, dass Ihre Mitarbeiter alles, was Sie tun, ignorieren oder verpassen werden ... Sie können nicht verhindern, dass der Code bei der Arbeit so unwirksam wird. Die Dokumentation scheint mir der einfachere, sauberere, billigere und ratsamere Weg zu sein. Andernfalls würde die deklarative Programmierung nicht existieren.
Laiv
1
Resharper hat das Attribut [UsedImplictly].
CodesInChaos
4
Ich denke, die Xml-Dokumentkommentaroption ist wahrscheinlich Ihre beste Option. Es ist kurz, selbstdokumentierend und benötigt keine "Hacks" oder zusätzlichen Definitionen.
Doc Brown
2
Eine weitere Abstimmung für Kommentare zur XML-Dokumentation. Wenn Sie trotzdem eine Dokumentation erstellen, sollte diese in der generierten Dokumentation hervorstechen.
Frank Hileman

Antworten:

12

Eine Kombination der vorgeschlagenen Lösungen:

  • Verwenden Sie XML-Dokumentations-Tags, um zu dokumentieren, dass der Konstruktor / die Methode über Reflection aufgerufen wird.
    Dies sollte den Kollegen (und meinem zukünftigen Selbst) die beabsichtigte Verwendung klarstellen.
  • Verwenden Sie den 'Trick' über das <see>-tag, um die Referenzanzahl für den Konstruktor / die Methode zu erhöhen.
    Dies zeigt, dass die Code-Linse und die Suchreferenzen zeigen, dass auf den Konstruktor / die Methode verwiesen wird.
  • Kommentieren Sie mit Resharper UsedImplicitlyAttribute
    • Resharper ist ein De-facto-Standard und [UsedImplicitly]hat genau die beabsichtigte Semantik.
    • Wer Resharper nicht verwendet, kann die JetBrains ReSharper Annotations über NuGet:
      PM> installieren Install-Package JetBrains.Annotations.
  • Wenn es sich um eine private Methode handelt und Sie die Codeanalyse von Visual Studio verwenden, verwenden Sie diese SupressMessageAttributefür die Nachricht CA1811: Avoid uncalled private code.

Zum Beispiel:

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();
    }
}

Die Lösung überträgt die beabsichtigte Verwendung des Konstruktors sowohl an menschliche Leser als auch an die drei statischen Code-Analysesysteme, die am häufigsten mit C # und Visual Studio verwendet werden.
Der Nachteil ist, dass sowohl ein Kommentar als auch ein oder zwei Anmerkungen etwas überflüssig erscheinen.

Kasper van den Berg
quelle
Beachten Sie MeansImplicitUseAttribute, dass Sie damit auch eigene Attribute erstellen können, die sich UsedImplicitlyauswirken. Dies kann in den richtigen Situationen viel Attributrauschen reduzieren.
Dave Cousineau
Die Verbindung zu JetBrains ist unterbrochen.
John Zabroski
5

Ich hatte dieses Problem noch nie in einem .NET-Projekt, aber ich habe regelmäßig das gleiche Problem mit Java-Projekten. Mein üblicher Ansatz besteht darin, die @SuppressWarnings("unused")Anmerkung zu verwenden und einen Kommentar hinzuzufügen, der erklärt, warum (die Dokumentation des Grundes für das Deaktivieren von Warnungen ist Teil meines Standardcodestils - jedes Mal, wenn der Compiler etwas nicht herausfinden kann, gehe ich davon aus, dass es wahrscheinlich ist, dass ein Mensch Probleme hat zu). Dies hat den Vorteil, dass automatisch sichergestellt wird, dass statische Analysewerkzeuge wissen, dass der Code keine direkten Verweise enthalten soll, und dass für menschliche Leser ein detaillierter Grund angegeben wird.

Das C # -Äquivalent von Java @SuppressWarningsist SuppressMessageAttribute. Für private Methoden können Sie die Meldung CA1811 verwenden: Vermeiden Sie nicht aufgerufenen privaten Code . z.B:

class MyPlugin
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage(
        "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
        Justification = "Constructor is called via reflection")]
    private MyPlugin(int arg)
    {
        throw new NotImplementedException();
    }
}
Jules
quelle
(Ich weiß es nicht, aber ich gehe davon aus, dass die CLI ein Attribut unterstützt, das dem von mir erwähnten Java ähnelt. Wenn jemand weiß, was es ist, bearbeiten Sie bitte meine Antwort als Referenz darauf ...)
Jules
stackoverflow.com/q/10926385/5934037 sieht aus wie es ist.
Laiv
1
Ich habe kürzlich festgestellt, dass das Durchführen von Tests und das Messen der Abdeckung (in Java) ein guter Weg ist, um festzustellen, ob ein Codeblock wirklich nicht verwendet wird. Dann kann ich es entfernen oder nachsehen, warum es nicht verwendet wird (wenn ich das Gegenteil erwarte). Bei der Suche schenke ich den Kommentaren mehr Aufmerksamkeit.
Laiv
Es gibt die SuppressMessageAttribute( msdn.microsoft.com/en-us/library/… ). Die Nachricht, die am nächsten kommt, lautet CA1811: Avoid uncalled private code( msdn.microsoft.com/en-us/library/ms182264.aspx ); Ich habe noch keine Nachricht für den öffentlichen Code gefunden.
Kasper van den Berg
2

Eine Alternative zur Dokumentation wäre, Unit-Tests durchzuführen, um sicherzustellen, dass die Reflection-Aufrufe erfolgreich ausgeführt werden.

Auf diese Weise sollte Ihr Build- / Testprozess Sie benachrichtigen, wenn jemand die Methoden ändert oder entfernt, dass Sie etwas kaputt gemacht haben.

Nick Williams
quelle
0

Nun, ohne Ihren Code zu sehen, klingt es so, als wäre dies ein guter Ort, um eine Vererbung einzuführen. Vielleicht eine virtuelle oder abstrakte Methode, die der Konstruktor dieser Klassen aufrufen kann? Wenn Sie die Methode sind, die Sie markieren möchten, ist dies nur der Konstruktor, dann versuchen Sie wirklich, eine Klasse und keine Methode zu markieren, ja? In der Vergangenheit habe ich zum Markieren von Klassen eine leere Schnittstelle erstellt. Dann können Code-Inspektions-Tools und Refactoring nach Klassen suchen, die die Schnittstelle implementieren.

Jeff Sall
quelle
Wahre normale Vererbung wäre der richtige Weg. Und die Klassen sind tatsächlich durch Vererbung verwandt. Aber eine neue Instanz jenseits der Vererbung erstellen. Außerdem verlasse ich mich auf die Vererbung statischer Methoden, und da C # keine Klassen-Slots unterstützt; Es gibt einen Weg, den ich benutze, der jedoch den Rahmen dieses Kommentars sprengt.
Kasper van den Berg