Le migliori pratiche per contrassegnare un metodo che viene chiamato tramite la riflessione?


11

Il nostro software ha diverse classi che dovrebbero essere trovate dinamicamente tramite la riflessione. Tutte le classi hanno un costruttore con una firma specifica tramite la quale il codice di riflessione crea un'istanza di oggetti.
Tuttavia, quando qualcuno verifica se si fa riferimento al metodo (ad esempio tramite Visual Studio Code Lens), il riferimento tramite reflection non viene conteggiato. Le persone possono perdere i loro riferimenti e rimuovere (o modificare) metodi apparentemente inutilizzati.

Come dovremmo contrassegnare / documentare i metodi che devono essere chiamati tramite la riflessione?

Idealmente, il metodo dovrebbe essere contrassegnato in modo tale che sia i colleghi che Visual Studio / Roslyn e altri strumenti automatizzati "vedano" che il metodo deve essere chiamato tramite la riflessione.

Conosco due opzioni che possiamo usare ma entrambe non sono del tutto soddisfacenti. Poiché Visual Studio non è in grado di trovare i riferimenti:

  • Utilizzare un attributo personalizzato e contrassegnare il costruttore con questo attributo.
    • Un problema è che le proprietà degli attributi non possono essere un riferimento al metodo, quindi il costruttore mostrerà comunque come avere 0 riferimenti.
    • I colleghi che non hanno familiarità con l'attributo personalizzato probabilmente lo ignoreranno.
    • Un vantaggio del mio approccio attuale è che la parte di riflessione può usare l'attributo per trovare il costruttore che dovrebbe chiamare.
  • Utilizzare i commenti per documentare che un metodo / costruttore deve essere chiamato tramite reflection.
    • Gli strumenti automatici ignorano i commenti (e anche i colleghi potrebbero farlo).
    • I commenti alla documentazione Xml possono essere utilizzati per fare in modo che Visual Studio conteggi un riferimento aggiuntivo al metodo / costruttore:
      Sia MyPluginla classe il cui costruttore deve invocare tramite reflection. Supponiamo che il codice di riflessione invocante cerchi costruttori che accettano un intparametro. La seguente documentazione indica che il codice obiettivo mostra il costruttore con 1 riferimento:
      /// <see cref="MyPlugin.MyPlugin(int)"/> is invoked via reflection

Quali opzioni migliori esistono?
Qual è la migliore pratica per contrassegnare un metodo / costruttore che deve essere chiamato tramite la riflessione?


Giusto per essere chiari, questo è per una sorta di sistema di plugin, giusto?
whatsisname

2
Stai pensando che i tuoi colleghi ignorino o perdano tutto ciò che fai ... Non puoi impedire al codice di essere così inefficace sul lavoro. Documentare mi sembra il modo più semplice, più pulito, più economico e consigliabile. Altrimenti non esisterebbe la programmazione dichiarativa.
Laiv

1
Resharper ha l'attributo [UsedImplictly].
CodesInChaos

4
Immagino che l'opzione di commento doc Xml sia probabilmente la tua migliore opzione. È breve, auto-documentato e non necessita di "hack" o definizioni aggiuntive.
Doc Brown,

2
Un altro voto per i commenti sulla documentazione XML. Se stai comunque creando la documentazione, dovrebbe emergere nella documentazione generata.
Frank Hileman,

Risposte:


12

Una combinazione delle soluzioni suggerite:

  • Utilizzare i tag Documentazione XML per documentare che il costruttore / metodo viene chiamato tramite reflection.
    Ciò dovrebbe chiarire l'uso previsto per i colleghi (e il mio io futuro).
  • Usa il 'trucco' tramite il <see>-tag per aumentare il conteggio dei riferimenti per il costruttore / metodo.
    Questo fa sì che l'obiettivo del codice e trova i riferimenti mostrino che il costruttore / metodo è referenziato.
  • Annota con Resharper's UsedImplicitlyAttribute
    • Resharper è uno standard di fatto e [UsedImplicitly]ha precisamente la semantica prevista.
    • Coloro che non utilizzano Resharper possono installare le annotazioni ReSharper JetBrains tramite NuGet:
      PM> Install-Package JetBrains.Annotations.
  • Se si tratta di un metodo privato e si sta utilizzando l'analisi del codice di Visual Studio, utilizzare SupressMessageAttributeper il messaggio CA1811: Avoid uncalled private code.

Per esempio:

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 soluzione conferisce l'uso previsto del costruttore sia ai lettori umani che ai 3 sistemi di analisi del codice statico più utilizzati con C # e Visual Studio.
Il rovescio della medaglia è che sia un commento che una o due annotazioni potrebbero sembrare un po 'ridondanti.


Nota anche MeansImplicitUseAttributeche può essere usato per creare i tuoi attributi che hanno un UsedImplicitlyeffetto. Ciò può ridurre molto il rumore degli attributi nelle giuste situazioni.
Dave Cousineau,

Il collegamento a JetBrains è interrotto.
John Zabroski,

5

Non ho mai avuto questo problema in un progetto .Net, ma ho regolarmente lo stesso problema con i progetti Java. Il mio solito approccio è quello di utilizzare l' @SuppressWarnings("unused")annotazione aggiungendo un commento che spiega perché (documentare il motivo per disabilitare eventuali avvisi fa parte del mio stile di codice standard - ogni volta che il compilatore non riesce a capire qualcosa, presumo sia probabile che un essere umano possa lottare pure). Ciò ha il vantaggio di garantire automaticamente che gli strumenti di analisi statica siano consapevoli del fatto che il codice non dovrebbe avere riferimenti diretti e di fornire un motivo dettagliato per i lettori umani.

L'equivalente C # di Java @SuppressWarningsè SuppressMessageAttribute. Per i metodi privati è possibile utilizzare il messaggio CA1811: Evitare il codice privato non richiamato ; per esempio:

class MyPlugin
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage(
        "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
        Justification = "Constructor is called via reflection")]
    private MyPlugin(int arg)
    {
        throw new NotImplementedException();
    }
}

(Non lo so, ma presumo che la CLI supporti un attributo simile a quello di cui parlo; se qualcuno sa di cosa si tratta, modifica la mia risposta come riferimento ad essa ...)
Jules


1
Ho scoperto di recente che eseguire test e misurare la copertura (in Java) è un buon modo per sapere se un blocco di codice è davvero inutilizzato. Quindi posso rimuoverlo o vedere perché non viene utilizzato (se mi aspetto il contrario). Durante la ricerca è quando prendo più attenzione ai commenti.
Laiv

Esiste il SuppressMessageAttribute( msdn.microsoft.com/en-us/library/… ). Il messaggio più vicino è CA1811: Avoid uncalled private code( msdn.microsoft.com/en-us/library/ms182264.aspx ); Non ho ancora trovato un messaggio per il codice pubblico.
Kasper van den Berg,

2

Un'alternativa alla documentazione sarebbe quella di avere unit test (s) per assicurarsi che le chiamate di riflessione vengano eseguite correttamente.

In questo modo se qualcuno modifica o rimuove i metodi il tuo processo di compilazione / test dovrebbe avvisarti che hai rotto qualcosa.


0

Bene, senza vedere il tuo codice, sembra che questo sia un buon posto per introdurre un po 'di eredità. Forse un metodo virtuale o astratto che il costruttore di queste classi può chiamare? Se sei il metodo che stai cercando di contrassegnare è solo il costruttore, allora stai davvero cercando di contrassegnare una classe e non un metodo, vero? Qualcosa che ho fatto in passato per contrassegnare le classi è creare un'interfaccia vuota. Quindi gli strumenti di ispezione del codice e il refactoring possono cercare le classi che implementano l'interfaccia.


La vera eredità normalmente sarebbe la strada da percorrere. E le classi sono effettivamente legate per eredità. Ma creare una nuova istanza oltre l'eredità. Inoltre, mi affido all '"eredità" di metodi statici e poiché C # non supporta gli slot di classe; c'è un modo per aggirare, che io uso, ma che va oltre lo scopo di questo commento.
Kasper van den Berg,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.