| name | system-commandline-actions-and-invocation |
| version | 2.0.0 |
| description | Use when the action itself is the hard part — returning Task<int> from an async action, choosing the process exit code from what the action returns, and splitting Parse(args) from Invoke/InvokeAsync so the ParseResult can be inspected in between. SetHandler and positional binding are gone. Wiring a plain action and reading values are core shapes; declaring inputs and rejecting bad ones are separate topics. |
System.CommandLine: actions & invocation
Behavior attaches to a command with SetAction. You run the app by Parse-ing args into a
ParseResult, then Invoke/InvokeAsync. SetHandler, delegate parameter binding, and IConsole
were removed at GA — do not use them.
Do NOT web_search / web_fetch — nearly all samples show SetHandler(...) with
positionally-bound parameters, which no longer exists.
Required setup
System.CommandLine is not in the shared framework — add the package
(dotnet package add <proj> System.CommandLine), then using System.CommandLine;.
An action reads its inputs from the ParseResult by option/argument instance, so the instances
have to be in scope and added to the command. Nothing is injected into the delegate:
using System.CommandLine;
var nameOption = new Option<string>("--name") { Description = "Who to greet" };
var root = new RootCommand("Greeter");
root.Options.Add(nameOption);
root.SetAction(parseResult =>
{
string name = parseResult.GetValue(nameOption)!;
Console.WriteLine($"Hello, {name}!");
return 0;
});
return await root.Parse(args).InvokeAsync();
command, root and nameOption in the examples below refer to instances set up this way.
SetAction signatures
command.SetAction(parseResult =>
{
var name = parseResult.GetValue(nameOption)!;
Console.WriteLine($"Hello {name}");
return 0;
});
command.SetAction(async (parseResult, cancellationToken) =>
{
var url = parseResult.GetValue(urlOption)!;
await DoWorkAsync(url, cancellationToken);
return 0;
});
- Read every input from the
ParseResult by the option/argument instance:
parseResult.GetValue(theOption). No parameters are injected.
- The
int return value is the process exit code. Use the async overload whenever you await.
Parsing and running
return await root.Parse(args).InvokeAsync();
ParseResult result = root.Parse(args);
if (result.Errors.Count > 0)
{
foreach (var e in result.Errors) Console.Error.WriteLine(e.Message);
return 1;
}
return await result.InvokeAsync();
command.Parse(args) returns a ParseResult; Invoke() / InvokeAsync() then run the matched
command's action (and built-in --help / --version / error reporting).
- Migration:
command.Invoke(args) / InvokeAsync(args) (arg-taking overloads) are gone — always
Parse(args) first, then invoke the result.
Errors & exit codes
- User-input errors: report with
result.AddError("...") from a CustomParser or a validator on
the option/argument. They land in ParseResult.Errors, are printed by the invoker, and produce a
non-zero exit code automatically. Do not throw for bad input.
- Action outcome: return the exit code you want from
SetAction.
ParseResult.Errors is the list to check when you parse-then-decide manually.