Prática recomendada para marcar um método chamado via reflexão?

11

Nosso software possui várias classes que devem ser encontradas dinamicamente via reflexão. Todas as classes têm um construtor com uma assinatura específica através da qual o código de reflexão instancia objetos.
No entanto, quando alguém verifica se o método é referenciado (por exemplo, através do Visual studio Code Lens), a referência via reflexão não é contada. As pessoas podem perder suas referências e remover (ou alterar) métodos aparentemente não utilizados.

Como devemos marcar / documentar métodos destinados a serem chamados via reflexão?

Idealmente, o método deve ser marcado de forma que os colegas e o Visual Studio / Roslyn e outras ferramentas automatizadas 'vejam' que o método deve ser chamado por meio de reflexão.

Conheço duas opções que podemos usar, mas ambas não são satisfatórias. Como o Visual Studio não pode encontrar as referências:

  • Use um atributo personalizado e marque o construtor com este atributo.
    • Um problema é que as propriedades do Atributo não podem ser uma referência de método, portanto, o construtor ainda será exibido como tendo 0 referências.
    • Os colegas que não estão familiarizados com o atributo personalizado provavelmente o ignorarão.
    • Uma vantagem da minha abordagem atual é que a parte de reflexão pode usar o atributo para encontrar o construtor que deve chamar.
  • Use comentários para documentar que um método / construtor deve ser chamado por meio de reflexão.
    • As ferramentas automatizadas ignoram os comentários (e os colegas também podem fazê-lo).
    • Os comentários da documentação Xml podem ser usados ​​para que o Visual Studio conte uma referência adicional ao método / construtor:
      Seja MyPlugina classe cujo construtor deve chamar por meio de reflexão. Suponha que o código de reflexão de chamada procure por construtores que usam um intparâmetro. A documentação a seguir faz com que essa lente de código mostre o construtor com 1 referência:
      /// <see cref="MyPlugin.MyPlugin(int)"/> is invoked via reflection

Que melhores opções existem?
Qual é a melhor prática para marcar um método / construtor que se pretende chamar por reflexão?

Kasper van den Berg
fonte
Só para ficar claro, isso é para algum tipo de sistema de plugins, certo?
Whatsisname
2
Você está assumindo que seus colegas de trabalho vão ignorar ou perder tudo o que fazem ... Você não pode impedir que o código seja tão ineficaz no trabalho. Documentar me parece a maneira mais fácil, limpa, barata e aconselhável. Caso contrário, não existiria a programação declarativa.
LAIV
1
O compartilhador de novo possui o atributo [UsedImplictly].
código é o seguinte
4
Eu acho que a opção de comentário de documento XML é provavelmente a melhor opção. É curto, auto-documentado e não precisa de "hacks" ou definições adicionais.
Doc Brown
2
Outro voto para comentários da documentação xml. Se você estiver criando documentação de qualquer maneira, ela deve se destacar na documentação gerada.
precisa saber é o seguinte

Respostas:

12

Uma combinação das soluções sugeridas:

  • Use tags de documentação XML para documentar que o construtor / método é chamado via reflexão.
    Isso deve esclarecer o uso pretendido para os colegas (e meu futuro eu).
  • Use o 'truque' via <see>-tag para aumentar a contagem de referência para o construtor / método.
    Isso faz com que essa lente de código e as referências de localização mostrem que o construtor / método é referenciado.
  • Anote com Resharper UsedImplicitlyAttribute
    • O compartilhador é um padrão de fato e [UsedImplicitly]possui precisamente a semântica pretendida.
    • Quem não usa o Resharper pode instalar as anotações do JetBrains ReSharper via NuGet:
      PM> Install-Package JetBrains.Annotations.
  • Se for um método privado e você estiver usando a análise de código do Visual Studio, use SupressMessageAttributepara a mensagem CA1811: Avoid uncalled private code.

Por exemplo:

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

A solução confere o uso pretendido do construtor aos leitores humanos e aos 3 sistemas de análise de código estático mais usados ​​com C # e Visual Studio.
A desvantagem é que tanto um comentário quanto uma ou duas anotações podem parecer um pouco redundantes.

Kasper van den Berg
fonte
Observe também MeansImplicitUseAttributeque pode ser usado para criar seus próprios atributos que têm UsedImplicitlyefeito. Isso pode reduzir muito ruído de atributo nas situações certas.
Dave Cousineau 12/12
O link para o JetBrains está quebrado.
John Zabroski 1/11/19
5

Nunca tive esse problema em um projeto .Net, mas regularmente tenho o mesmo problema em projetos Java. Minha abordagem usual é usar a @SuppressWarnings("unused")anotação adicionando um comentário explicando o motivo (documentar o motivo da desativação de qualquer aviso faz parte do meu estilo de código padrão - sempre que o compilador não conseguir descobrir algo, presumo que é provável que um humano possa ter problemas também). Isso tem a vantagem de garantir automaticamente que as ferramentas de análise estática estejam cientes de que o código não deve ter referências diretas e fornecer um motivo detalhado para os leitores humanos.

O equivalente em C # de Java @SuppressWarningsé SuppressMessageAttribute. Para métodos particulares , você pode usar a mensagem CA1811: Evite código privado desnecessário ; por exemplo:

class MyPlugin
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage(
        "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
        Justification = "Constructor is called via reflection")]
    private MyPlugin(int arg)
    {
        throw new NotImplementedException();
    }
}
Jules
fonte
(Eu não sei, mas presumo que a CLI suporte um atributo semelhante ao Java que eu mencionei; se alguém souber o que é, edite minha resposta como uma referência a ele ...)
Jules
1
Descobri recentemente que a realização de testes e cobertura de medições (em java) é uma boa maneira de saber se um bloco de código é realmente não utilizado. Então eu posso removê-lo ou ver por que Não está sendo usado (se espero o contrário). Enquanto a pesquisa é quando presto mais atenção aos comentários.
LAIV
Existe o SuppressMessageAttribute( msdn.microsoft.com/en-us/library/… ). A mensagem mais próxima é CA1811: Avoid uncalled private code( msdn.microsoft.com/en-us/library/ms182264.aspx ); Ainda não encontrei uma mensagem para código público.
Kasper van den Berg
2

Uma alternativa à documentação seria ter testes de unidade, certificando-se de que as chamadas de reflexão sejam executadas com êxito.

Dessa forma, se alguém alterar ou remover os métodos, seu processo de construção / teste deve alertá-lo de que você quebrou alguma coisa.

Nick Williams
fonte
0

Bem, sem ver seu código, parece que esse seria um bom lugar para introduzir alguma herança. Talvez um método virtual ou abstrato que o construtor dessas classes possa chamar? Se você é o método que você está tentando marcar é apenas o construtor, então você está realmente tentando marcar uma classe e não um método, sim? Algo que eu fiz no passado para marcar classes é criar uma interface vazia. Em seguida, as ferramentas de inspeção de código e a refatoração podem procurar classes que implementam a interface.

Jeff Sall
fonte
A verdadeira herança normalmente seria o caminho a seguir. E as classes são realmente relacionadas por herança. Mas criar uma nova instância além da herança. Além disso, eu confio na 'herança' de métodos estáticos e, como o C # não suporta slots de classe; existe uma maneira de contornar, que eu uso, mas que está além do escopo deste comentário.
Kasper van den Berg