Skip to content

Does the Options attribute class really need to be sealed? #859

@SJFriedl

Description

@SJFriedl

I have been using CommandLineParser for a while now (and it's changed how I build tools -- thank you!) including in a program with ~50 Verbs (and growing), and since many of the Verbs take common options, I was hoping to extend Options to provide common handling of them.

Simple example: if 15 Verbs take a LogFile option, each of which has a long name ("log-file") and a help description, I can do this now by constant strings to keep them consistent, but if I were able to do:

[AttributeUsage(AttributeTargets.Property)]
public class LogFileOptionAttribute : OptionAttribute
{
    public LogFileOptionAttribute(bool required = false)
        : base("log-file") // I don't use short option names for anything
    {
        HelpText = "Name of the logfile to use for blah blah blah";
        Required = required;
    }
}

and then adorn the actual option with:

public class BlahOptions {
    ...
    [LogFileOption]
    public string LogFileName {get ; set; }
    ...
}

I have easily a dozen of these common options. It's not a huge deal to create constant strings:

    [Option(OPTION_LOGFILE_NAME, HelpText = OPTION_LOGFILE_HELPTEXT)]

but adding this level of abstraction would be more convenient.

If there's a good reason for this class to be sealed, then so be it, but mine doesn't feel like a ridiculous use case. I may well get to 100 verbs for this tool by the time I'm done.

Activity

ericnewton76

ericnewton76 commented on Mar 7, 2023

@ericnewton76
Member

The core code of the parser is only looking for "OptionsAttribute" not "LogFileOptionAttribute" types...

Submit a pull request with some initial work to enable this feature and we would consider it.

SJFriedl

SJFriedl commented on Mar 13, 2023

@SJFriedl
Author

I think reflection looking for an attribute will/can get derived classes of that attribute as well, but this is clearly on me to demonstrate. Will dig in.

SJFriedl

SJFriedl commented on Jun 11, 2023

@SJFriedl
Author

I finally got around to digging in, and after making literally one change - remove sealed from OptionAttribute, this all works super well.

The current code does in fact look for derived types because in Core/ReflectionExtensions.cs the GetSpecifications<T> method uses GetCustomAttributes with a true parameter: this is the inherit flag. The MSFT docs say it looks for ancestors but I believe it's actually looking for descendant classes. Maybe I'm confused about the terminology.

In any case, using (say) [LogFileOption] works great defining the long name (--log-file) and the help text in a common way, and one can still adorn it with Required=t/f flag in the usual way. This doesn't appear to impact the normal use of options, they all work as expected.

This also works, surprisingly, when LogFileOptionAttribute is defined in a different assembly than the option class that refers to it - reflection sometimes has guardrails on just how far it's going to look for things. I have a common library of supporting code that feeds multiple projects, including the command-line support.

The VerbAttribute is already non-sealed - the code shows this as having been considered but commented out - and I did not change the sealed value for ValueAttribute because I didn't need to extend it, and wanted to keep the changes limited to one thing. But I think that removing sealed from ValueAttribute is probably worth doing as well.

I'm doing my dev in Visual Studio 2022, with a large suite of C# .NET 6 code, and I compiled the CommandLine stuff with netstandard2.0; I have no real experience with cross-framework development so don't know the ins and outs of mixing and matching, and of course have not considered the F# stuff at all. I'm running my code on Windows 11.

Overall, it all Just Works(tm), and I haven't found a downside or gotcha yet.

I'm going to keep using this for a little while to shake it out, then will see if I can figure out how to make a pull request to make it so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @ericnewton76@SJFriedl

        Issue actions

          Does the Options attribute class really need to be sealed? · Issue #859 · commandlineparser/commandline