¿Mejor práctica para marcar un método que se llama a través de la reflexión?

11

Nuestro software tiene varias clases que se deben encontrar dinámicamente a través de la reflexión. Todas las clases tienen un constructor con una firma específica a través de la cual el código de reflexión crea instancias de objetos.
Sin embargo, cuando alguien comprueba si se hace referencia al método (por ejemplo, a través de la lente de código de Visual Studio), la referencia a través de la reflexión no se cuenta. Las personas pueden perder sus referencias y eliminar (o cambiar) métodos aparentemente no utilizados.

¿Cómo debemos marcar / documentar los métodos destinados a ser llamados a través de la reflexión?

Idealmente, el método debe marcarse de tal manera que tanto los colegas como Visual Studio / Roslyn y otras herramientas automatizadas "vean" que el método debe llamarse mediante reflexión.

Sé de dos opciones que podemos usar, pero ambas no son del todo satisfactorias. Como Visual Studio no puede encontrar las referencias:

  • Use un atributo personalizado y marque el constructor con este atributo.
    • Un problema es que las propiedades de atributo no pueden ser una referencia de método, por lo tanto, el constructor seguirá mostrando que tiene 0 referencias.
    • Los colegas que no estén familiarizados con el atributo personalizado probablemente lo ignorarán.
    • Una ventaja de mi enfoque actual es que la parte de reflexión puede usar el atributo para encontrar el constructor al que debería llamar.
  • Utilice los comentarios para documentar que un método / constructor está destinado a llamarse mediante reflexión.
    • Las herramientas automatizadas ignoran los comentarios (y los colegas también podrían hacerlo).
    • Los Comentarios de documentación Xml se pueden usar para que Visual Studio cuente una referencia adicional al método / constructor:
      Sea MyPluginla clase cuyo constructor invocar mediante reflexión. Suponga que el código de reflexión de invocación busca constructores que toman un intparámetro. La siguiente documentación muestra que la lente de código muestra que el constructor tiene 1 referencia:
      /// <see cref="MyPlugin.MyPlugin(int)"/> is invoked via reflection

¿Qué mejores opciones existen?
¿Cuál es la mejor práctica para marcar un método / constructor que se pretende llamar mediante reflexión?

Kasper van den Berg
fuente
Para ser claros, esto es para algún tipo de sistema de complemento, ¿verdad?
whatsisname
2
Estás asumiendo que tus compañeros de trabajo van a ignorar o extrañar todo lo que haces ... No puedes evitar que el código sea tan ineficaz en el trabajo. Documentar me parece la forma más fácil, limpia, económica y aconsejable. De lo contrario no existiría la programación declarativa.
Laiv
1
Resharper tiene el atributo [UsedImplictly].
CodesInChaos
44
Supongo que la opción de comentarios de documentos Xml es probablemente su mejor opción. Es breve, se documenta por sí mismo y no necesita ningún "truco" ni definiciones adicionales.
Doc Brown
2
Otro voto para comentarios de documentación xml. Si de todos modos está creando documentación, debe destacarse en la documentación generada.
Frank Hileman el

Respuestas:

12

Una combinación de las soluciones sugeridas:

  • Utilice etiquetas de documentación XML para documentar que el constructor / método se llama mediante reflexión.
    Esto debería aclarar el uso previsto para los colegas (y mi futuro yo).
  • Use el 'truco' a través de la <see>etiqueta para aumentar el recuento de referencias para el constructor / método.
    Esto hace que la lente de código y las referencias de búsqueda muestren que se hace referencia al constructor / método.
  • Anotar con Resharper's UsedImplicitlyAttribute
    • Resharper es un estándar de facto y [UsedImplicitly]tiene precisamente la semántica prevista.
    • Aquellos que no usan Resharper pueden instalar las anotaciones de JetBrains ReSharper a través de NuGet:
      PM> Install-Package JetBrains.Annotations.
  • Si es un método privado y está utilizando el análisis de código de Visual Studio, utilícelo SupressMessageAttributepara el mensaje CA1811: Avoid uncalled private code.

Por ejemplo:

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 solución confiere el uso previsto del constructor tanto a los lectores humanos como a los 3 sistemas de análisis de código estático más utilizados con C # y Visual Studio.
La desventaja es que tanto un comentario como una o dos anotaciones pueden parecer un poco redundantes.

Kasper van den Berg
fuente
Tenga en cuenta que también MeansImplicitUseAttributese puede usar para crear sus propios atributos que tengan un UsedImplicitlyefecto. Esto puede reducir mucho ruido de atributos en las situaciones correctas.
Dave Cousineau
El enlace a JetBrains está roto.
John Zabroski el
5

Nunca he tenido este problema en un proyecto .Net, pero regularmente tengo el mismo problema con los proyectos Java. Mi enfoque habitual allí es usar la @SuppressWarnings("unused")anotación agregando un comentario que explique por qué (documentar la razón por la que se desactivan las advertencias es parte de mi estilo de código estándar; cada vez que el compilador no puede resolver algo, supongo que es probable que un humano tenga dificultades también). Esto tiene la ventaja de garantizar automáticamente que las herramientas de análisis estático sean conscientes de que no se supone que el código tenga referencias directas, y de dar una razón detallada para los lectores humanos.

El equivalente de C # de Java @SuppressWarningses SuppressMessageAttribute. Para métodos privados , puede usar el mensaje CA1811: Evite el código privado no llamado ; p.ej:

class MyPlugin
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage(
        "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
        Justification = "Constructor is called via reflection")]
    private MyPlugin(int arg)
    {
        throw new NotImplementedException();
    }
}
Jules
fuente
(No lo sé, pero supongo que la CLI admite un atributo que es similar al Java que menciono; si alguien sabe de qué se trata, edite mi respuesta como referencia ...)
Julio
1
Recientemente descubrí que realizar pruebas y cobertura de medición (en java) es una buena manera de saber si un bloque de código realmente no se utiliza. Entonces puedo eliminarlo o mirar por qué no se usa (si espero lo contrario). Mientras busco es cuando pongo más atención a los comentarios.
Laiv
Existe el SuppressMessageAttribute( msdn.microsoft.com/en-us/library/… ). El mensaje que más se acerca es CA1811: Avoid uncalled private code( msdn.microsoft.com/en-us/library/ms182264.aspx ); Todavía no he encontrado un mensaje para el código público.
Kasper van den Berg
2

Una alternativa a la documentación sería realizar pruebas unitarias para asegurarse de que las llamadas de reflexión se ejecuten correctamente.

De esa manera, si alguien cambia o elimina los métodos, su proceso de compilación / prueba debería alertarlo de que ha roto algo.

Nick Williams
fuente
0

Bueno, sin ver su código, parece que este sería un buen lugar para introducir alguna herencia. ¿Quizás un método virtual o abstracto que el constructor de estas clases pueda llamar? Si eres el método que estás tratando de marcar es solo el constructor, entonces realmente estás tratando de marcar una clase y no un método, ¿sí? Algo que he hecho en el pasado para marcar clases es hacer una interfaz vacía. Luego, las herramientas de inspección de código y la refactorización pueden buscar clases que implementen la interfaz.

Jeff Sall
fuente
La verdadera herencia normal sería el camino a seguir. Y las clases están de hecho relacionadas por herencia. Pero creando una nueva instancia más allá de la herencia. Además, confío en la "herencia" de los métodos estáticos, y dado que C # no admite ranuras de clase; Hay una forma de evitarlo, que yo uso, pero eso está más allá del alcance de este comentario.
Kasper van den Berg