10

Our software has several classes that should be dynamically found via reflection. The classes all have a constructor with a specific signature via which the reflection code instantiates objects.
However, when someone checks whether the method is referenced (for example via Visual studio Code Lens), the reference via reflection are not counted. People can miss their references and remove (or change) apparently unused methods.

How should we mark/document methods intended to be called via reflection?

Ideally, the method should be marked in such a way that both colleagues and Visual Studio/Roslyn and other automated tools 'see' that the method is intended to be called via reflection.

I know of two options that we can use but both are not quite satisfying. Since Visual Studio cannot find the references:

  • Use a custom Attribute and mark the constructor with this attribute.
    • A problem is that Attribute properties cannot be a method reference, therefore the constructor will still show as having 0 references.
    • Colleagues not familiar with the custom attribute will probably ignore it.
    • An advantage of my current approach is the reflection part can use the attribute to find the constructor it should call.
  • Use comments to document that a method/constructor is intended to be called via reflection.
    • Automated tools ignore comments (and colleagues might do so as well).
    • Xml Documentation Comments can be used to have Visual Studio count an additional reference to the method/constructor:
      Let MyPlugin be the class whose constructor to invoke via reflection. Assume the invoking reflection code searches for constructors that take an int parameter. The following documentation makes that code lens shows the constructor having 1 reference:
      /// <see cref="MyPlugin.MyPlugin(int)"/> is invoked via reflection

Which better options exist?
What is the best-practice for marking a method/constructor that is intended to be called via reflection?

  • Just to be clear, this is for some sort of plugin system, right? – whatsisname Apr 24 '17 at 15:39
  • 2
    You are asuming that your co-workers are going to ignore or miss everything you do... You can not prevent the code from such ineffectiveness at work. Documenting seems to me the easier, cleaner, cheaper and advisable way. Otherwise would not exist the declarative programming. – Laiv Apr 24 '17 at 17:51
  • 1
    Resharper has the [UsedImplictly] attribute. – CodesInChaos Apr 24 '17 at 18:48
  • 4
    I guess the Xml doc comment option is probably your best option. It is short, self-documenting, and does not need any "hacks" or additional definitions. – Doc Brown Apr 24 '17 at 19:00
  • 2
    Another vote for xml documentation comments. If you are creating documentation anyway, it should stand out in the generated documentation. – Frank Hileman Apr 24 '17 at 21:02
10

A combination of the suggested solutions:

  • Use XML Documentation tags to document that the constructor/method is called via reflection.
    This should clarify the intended usage to colleagues (and my future self).
  • Use the 'trick' via the <see>-tag to increase the reference count for the constructor/method.
    This makes that code lens and find references show that the constructor/method is referenced.
  • Annotate with Resharper's UsedImplicitlyAttribute
    • Resharper is a de facto standard and [UsedImplicitly] has precisely the intended semantics.
    • Those not using Resharper can install the JetBrains ReSharper Annotations via NuGet:
      PM> Install-Package JetBrains.Annotations.
  • If it is a private method and you are using Visual Studio's code analysis, use SupressMessageAttribute for the message CA1811: Avoid uncalled private code.

For example:

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

The solution confers the intended use of the constructor to both human readers and to the 3 static code analysis systems most used with C# and Visual Studio.
The downside is that both a comment and one or two annotations might seem a bit redundant.

  • Note there is also MeansImplicitUseAttribute that can be used to make your own attributes that have a UsedImplicitly effect. This can reduce a lot of attribute noise in the right situations. – Dave Cousineau Dec 12 '18 at 22:46
4

I've never had this issue in a .Net project, but I regularly have the same issue with Java projects. My usual approach there is to use the @SuppressWarnings("unused") annotation adding a comment explaining why (documenting the reason for disabling any warnings is part of my standard code style - any time the compiler can't figure something out, I assume it's likely that a human might struggle too). This has the advantage of automatically ensuring static analysis tools are aware that the code is not supposed to have direct references, and giving a detailed reason for human readers.

The C# equivalent of Java's @SuppressWarnings is SuppressMessageAttribute. For private methods you can use the message CA1811: Avoid uncalled private code; e.g.:

class MyPlugin
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage(
        "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
        Justification = "Constructor is called via reflection")]
    private MyPlugin(int arg)
    {
        throw new NotImplementedException();
    }
}
  • (I don't know, but presume that the CLI supports an attribute that is similar to the Java one I mention; if anyone knows what it is, please edit my answer to as a reference to it...) – Jules Apr 24 '17 at 14:49
  • stackoverflow.com/q/10926385/5934037 looks like It's. – Laiv Apr 24 '17 at 17:35
  • 1
    I have discovered recently that performing tests and mesuring coverage (in java) is a good way to know if a block of code is really unused. Then I can remove it or look why Is unused (if I expect the opposite). While searching is when I pay more attention to the comments. – Laiv Apr 24 '17 at 21:00
  • There exists the SuppressMessageAttribute (msdn.microsoft.com/en-us/library/…). The message that comes closest is CA1811: Avoid uncalled private code (msdn.microsoft.com/en-us/library/ms182264.aspx); I have not yet found a message for public code. – Kasper van den Berg Apr 25 '17 at 6:26
2

An alternative to documenting would be to have unit test(s) making sure that the reflection calls run successfully.

That way if someone changes or removes the methods your build/test process should alert you that you have broken something.

0

Well without seeing your code, it kind of sounds like this would be a good place to introduce some inheritance. Maybe a virtual or abstract method that the constructor of these classes can call? If you are the method you are trying to mark is just the constructor, then you are really trying to mark a class and not a method yeah? Something I've done in the past to mark classes is to make an empty interface. Then code inspections tools and refactoring can look for classes that implement the interface.

  • True normally inheritance would be the way go. And the classes are indeed related by inheritance. But creating a new instance in beyond inheritance. Besides, I rely on 'inheritance' of static methods, and since C# does not support class slots; there is a way around, which I use, but that is beyond the scope of this comment. – Kasper van den Berg Apr 24 '17 at 18:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.