System.CommandLine — parse & dispatch a .NET CLI (current API)
System.CommandLine (namespace System.CommandLine) parses arguments into a command tree and runs
an action. The 2.0 GA redesign removed the old invocation/binding stack, so most remembered
snippets do not compile. Pin the current shapes below.
Use the System.CommandLine skills, not the web. Do NOT
web_search/web_fetchfor System.CommandLine usage — the web is dominated by the pre-GA beta API (SetHandler,AddOption,AddCommand,BinderBase<T>,IConsole,getDefaultValue:ctor args) that no longer exists. These skills are the current, authoritative API. This skill covers the core pattern; beta→GA migration, options/arguments in depth, actions/invocation, subcommands/help, and 3.x additions are covered separately.
The core pattern (current API)
using System.CommandLine;
// 1. Declare options/arguments. KEEP the instances — you read values back by identity.
var nameOption = new Option<string>("--name") // "--name" is the name; extra strings are ALIASES
{
Description = "Who to greet", // description is a PROPERTY, not a ctor arg
Required = true,
};
nameOption.Aliases.Add("-n");
var countOption = new Option<int>("--count") { DefaultValueFactory = _ => 1 };
// 2. Build the command tree.
var root = new RootCommand("Greeter sample");
root.Options.Add(nameOption);
root.Options.Add(countOption);
// 3. Wire behavior with SetAction; read parsed values from the ParseResult by instance.
root.SetAction(parseResult =>
{
string name = parseResult.GetValue(nameOption)!;
int count = parseResult.GetValue(countOption);
for (int i = 0; i < count; i++) Console.WriteLine($"Hello, {name}!");
return 0; // exit code
});
// 4. Parse then invoke.
return await root.Parse(args).InvokeAsync();
Gotchas (compile-clean but wrong, or removed-API)
new Option<T>("--name", "description")is WRONG. The 2nd positional arg is an alias, so the description becomes a bogus alias and the help text is lost. Usenew Option<T>("--name") { Description = "..." }; pass real aliases as extra strings (new Option<T>("--name", "-n")) or via.Aliases.Add(...). Same forArgument<T>.- Read values by identity.
parseResult.GetValue(theOptionInstance)— keep the exact instance you added. There is no delegate-parameter binding anymore. SetHandleris gone. UseSetAction(parseResult => ...)(sync) orSetAction(async (parseResult, ct) => ...)(async).AddOption/AddArgument/AddCommandare gone. Use the.Options/.Arguments/.Subcommandscollections:root.Options.Add(o),cmd.Subcommands.Add(sub).Required, notIsRequired. Default values areDefaultValueFactory = _ => v, notgetDefaultValue:/SetDefaultValue(...).IConsole/BinderBase<T>/HelpBuilderare gone. UseConsoledirectly; customize help via aHelpAction(help customization is covered separately).