ReflectiveArgMapping 2.1.0

dotnet add package ReflectiveArgMapping --version 2.1.0
                    
NuGet\Install-Package ReflectiveArgMapping -Version 2.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ReflectiveArgMapping" Version="2.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ReflectiveArgMapping" Version="2.1.0" />
                    
Directory.Packages.props
<PackageReference Include="ReflectiveArgMapping" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ReflectiveArgMapping --version 2.1.0
                    
#r "nuget: ReflectiveArgMapping, 2.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ReflectiveArgMapping@2.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ReflectiveArgMapping&version=2.1.0
                    
Install as a Cake Addin
#tool nuget:?package=ReflectiveArgMapping&version=2.1.0
                    
Install as a Cake Tool

Reflective Commandline Argument Mapping

An over-complicated system binding commandline arguments to runtime states.

Features & Motivation

The framework is motivated by the following requirements:

Automatic property binding

Automatically assign values to command handlers, so the framework handles work such as type checking and name mapping. It's implemented by using attributes to map properties to names, then use one of the following ways to get a way to map strings to the value:

  1. user-provided transform function: add a small lambda explaining how this value should be parsed in the attribute
  2. IParsable<T> interface: the built-in way of declaring default deserialization logic for data types

State propagation

Subcommands of the same super command should be able to share the logic in initializing and disposing their common super command. For example, a super command may include logic to initiate and terminate connection to a network database. Subcommands of this command, naturally, might have something to do with the database, and it'll make the code a lot simpler if the common super command logic is inherited in the subcommands.

As the above text suggests, this is implemented by making subcommands derived types of their super commands.

Limitations

Current implementation has the following limitations:

  1. Performance: I only wrote this library for IO-bound programs' start up logic. Performance of reflection and frequent unboxing might not suffice on a hot path.

  2. Root command: while it's kind of weird, the concept of "root command" is not really "the common ancestor of every command". It's just a command that's registered when all other commands can't be matched. Further, none of the commands added to the executor are required to have a common root. Each command is treated completely independently, each with its own root. Rather than a limitation this is more like a linguistical quirk: it's actually more work, nothing more secure, and less flexibility to make things otherwise.

Sample usage

private class RootCommand : CommandHandler<int>
{
    public override void Initialize()
    {
        Console.WriteLine($"Initialize state: {State}");
    }

    public override int Execute()
    {
        Console.WriteLine($"Hello world from {State}");
        return 0;
    }
    
    [CommandLineOption("--state")] public string State { get; set; } = string.Empty;

    protected override void DisposeCore()
    {
        Console.WriteLine($"Disposed state: {State}");
    }
}

[NamedCommand("yell")]
private class YellCommand : RootCommand
{
    [CommandLineOption("--context")] 
    public string Context { get; set; } = "n/a";
    
    public override int Execute()
    {
        Console.WriteLine($"({Context}) HELLO!!!!");
        return 1;
    }
}

[NamedCommand("mult")]
private class YellMultCommand : YellCommand
{
    [CommandLineOption("--time")] 
    public int Time { get; set; } = 1;

    public override int Execute()
    {
        for (int i = 0; i < Time; i++)
        {
            base.Execute();
        }

        return 2;
    }
}
    
Assert.That(ReflectiveCommand
    .WithRoot<RootCommand, int>()
    .WithCommand<YellCommand>()
    .WithCommand<YellMultCommand>()
    .Execute(["yell", "--context", "in hurry", "--state", "db-connect"]), Is.EqualTo(1));

Assert.That(ReflectiveCommand
    .WithRoot<RootCommand, int>()
    .WithCommand<YellCommand>()
    .WithCommand<YellMultCommand>()
    .Execute(["yell", "mult", "--context", "in hurry", "--state", "db-connect", "--time", "10"]), Is.EqualTo(2));
private class RootCommand : AsyncCommandHandler<int>
{
    [CommandLineOption("--state")] public string State { get; set; } = string.Empty;
    
    public override Task Initialize()
    {
        Console.WriteLine($"Initialize state: {State}");
        return Task.CompletedTask;
    }

    public override async Task<int> ExecuteAsync()
    {
        await Task.Delay(10).ConfigureAwait(false);
        Console.WriteLine($"Hello world from {State}");
        return 0;
    }

    protected override ValueTask DisposeAsyncCore()
    {
        Console.WriteLine($"Dispose state: {State}");
        return ValueTask.CompletedTask;
    }
}

[NamedCommand("yell")]
private class YellCommand : RootCommand
{
    [CommandLineOption("--context")] 
    public string Context { get; set; } = "n/a";
    
    public override async Task<int> ExecuteAsync()
    {
        await Task.Delay(10);
        Console.WriteLine($"({Context}) HELLO!!!!");
        return 1;
    }
}

[NamedCommand("mult")]
private class YellMultCommand : YellCommand
{
    [CommandLineOption("--time")] 
    public int Time { get; set; } = 1;

    public override async Task<int> ExecuteAsync()
    {
        await Task.Delay(10);
        
        for (int i = 0; i < Time; i++)
        {
            await base.ExecuteAsync().ConfigureAwait(false);
        }

        return 2;
    }
}

Assert.That(await ReflectiveCommand
    .WithAsyncRoot<RootCommand, int>()
    .WithCommand<YellCommand>()
    .WithCommand<YellMultCommand>()
    .ExecuteAsync(["yell", "--context", "in hurry", "--state", "db-connect"]).ConfigureAwait(false), Is.EqualTo(1));

Assert.That(await ReflectiveCommand
    .WithAsyncRoot<RootCommand, int>()
    .WithCommand<YellCommand>()
    .WithCommand<YellMultCommand>()
    .ExecuteAsync(["yell", "mult", "--context", "in hurry", "--state", "db-connect", "--time", "10"]).ConfigureAwait(false), Is.EqualTo(2));
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.1.0 142 4/30/2026
2.0.0 95 4/29/2026
1.0.1 97 4/28/2026
1.0.0 104 4/28/2026

fix a problem with handling default values