Table of Contents

Migrating System.CommandLine from 2.0.0-beta4 to 2.0.2

This guide covers breaking changes when upgrading from System.CommandLine version 2.0.0-beta4.22272.1 to 2.0.2. The stable release introduced significant API changes, particularly around validators, handlers, and symbol configuration.

Validator Changes

AddValidator → Validators.Add

The AddValidator method was replaced with a mutable Validators collection property.

// Beta4
command.AddValidator(validator);
option.AddValidator(validator);
argument.AddValidator(validator);

// 2.0.2
command.Validators.Add(validator);
option.Validators.Add(validator);
argument.Validators.Add(validator);

ErrorMessage Property → AddError Method

The SymbolResult.ErrorMessage property was converted to an AddError() method to support multiple errors per symbol.

// Beta4
option.AddValidator(result =>
{
    if (result.GetValueForOption(option) < 1)
    {
        result.ErrorMessage = "Must be greater than 0";
    }
});

// 2.0.2
option.Validators.Add(result =>
{
    if (result.GetValue(option) < 1)
    {
        result.AddError("Must be greater than 0");
    }
});

Complete Validator Example

// Beta4
public partial class MyCommand {
    partial void Initialize() {
        this.Option_Count.AddValidator(r => {
            var value = r.GetValueForOption(this.Option_Count);
            if (value < 0) {
                r.ErrorMessage = "Count cannot be negative";
            }
        });
    }
}

// 2.0.2
public partial class MyCommand {
    partial void Initialize() {
        this.Option_Count.Validators.Add(r => {
            var value = r.GetValue(this.Option_Count);
            if (value < 0) {
                r.AddError("Count cannot be negative");
            }
        });
    }
}

Command Validators for Mutually Exclusive Options

// Beta4
command.AddValidator(commandResult =>
{
    var hasOne = commandResult.Children.Any(sr => sr is OptionResult or && or.Option.Name == "--one");
    var hasTwo = commandResult.Children.Any(sr => sr is OptionResult or && or.Option.Name == "--two");
    if (hasOne && hasTwo)
    {
        commandResult.ErrorMessage = "Options '--one' and '--two' cannot be used together.";
    }
});

// 2.0.2
command.Validators.Add(commandResult =>
{
    var hasOne = commandResult.Children.Any(sr => sr is OptionResult or && or.Option.Name == "--one");
    var hasTwo = commandResult.Children.Any(sr => sr is OptionResult or && or.Option.Name == "--two");
    if (hasOne && hasTwo)
    {
        commandResult.AddError("Options '--one' and '--two' cannot be used together.");
    }
});

Handler Changes

SetHandler → SetAction

The SetHandler method was replaced with SetAction, and ICommandHandler was replaced with CommandLineAction.

// Beta4
rootCommand.SetHandler((InvocationContext context) =>
{
    string? value = context.ParseResult.GetValueForOption(option);
});

// 2.0.2
rootCommand.SetAction((ParseResult parseResult) =>
{
    string? value = parseResult.GetValue(option);
});

Async Handlers

CancellationToken is now a mandatory parameter for async actions:

// Beta4
rootCommand.SetHandler(async (InvocationContext context) =>
{
    var token = context.GetCancellationToken();
    await DoWorkAsync(token);
});

// 2.0.2
rootCommand.SetAction(async (ParseResult parseResult, CancellationToken token) =>
{
    await DoWorkAsync(token);
});

InvocationContext Removed

InvocationContext was eliminated. ParseResult and CancellationToken are now passed directly:

// Beta4
rootCommand.SetHandler((InvocationContext context) =>
{
    var parseResult = context.ParseResult;
    var token = context.GetCancellationToken();
    var value = parseResult.GetValueForOption(myOption);
});

// 2.0.2
rootCommand.SetAction((ParseResult parseResult, CancellationToken token) =>
{
    var value = parseResult.GetValue(myOption);
});

Option and Argument Changes

Mandatory Name Parameter

Constructors now require the name parameter:

// Beta4
Option<int> option = new();
option.Name = "--number";

// 2.0.2
Option<int> option = new("--number");

Aliases in Constructor

Aliases can now be specified directly in the constructor:

// Beta4
Option<bool> option = new("--help", "An option with aliases.");
option.Aliases.Add("-h");
option.Aliases.Add("/h");

// 2.0.2
Option<bool> option = new("--help", "-h", "/h")
{
    Description = "An option with aliases."
};

SetDefaultValue → DefaultValueFactory

// Beta4
Option<int> option = new("--number");
option.SetDefaultValue(42);

// 2.0.2
Option<int> option = new("--number")
{
    DefaultValueFactory = _ => 42
};

Custom Parsing with CustomParser

// Beta4
Argument<Uri> uri = new("arg", parse: result =>
{
    if (!Uri.TryCreate(result.Tokens.Single().Value, UriKind.RelativeOrAbsolute, out var uriValue))
    {
        result.ErrorMessage = "Invalid URI format.";
        return null;
    }
    return uriValue;
});

// 2.0.2
Argument<Uri> uri = new("arg")
{
    CustomParser = result =>
    {
        if (!Uri.TryCreate(result.Tokens.Single().Value, UriKind.RelativeOrAbsolute, out var uriValue))
        {
            result.AddError("Invalid URI format.");
            return null;
        }
        return uriValue;
    }
};

ParseResult Changes

GetValueForOption → GetValue

// Beta4
var value = parseResult.GetValueForOption(option);
var argValue = parseResult.GetValueForArgument(argument);

// 2.0.2
var value = parseResult.GetValue(option);
var argValue = parseResult.GetValue(argument);

FindResultFor → GetResult

// Beta4
var optionResult = parseResult.FindResultFor(option);

// 2.0.2
var optionResult = parseResult.GetResult(option);

Getting Values by Name

// 2.0.2
int number = parseResult.GetValue<int>("--number");

Migration Checklist

Beta4 2.0.2
symbol.AddValidator(delegate) symbol.Validators.Add(delegate)
result.ErrorMessage = "msg" result.AddError("msg")
result.GetValueForOption(opt) result.GetValue(opt)
result.GetValueForArgument(arg) result.GetValue(arg)
parseResult.FindResultFor(symbol) parseResult.GetResult(symbol)
command.SetHandler(action) command.SetAction(action)
command.Handler command.Action
InvocationContext context ParseResult parseResult, CancellationToken token
context.ParseResult parseResult (direct parameter)
context.GetCancellationToken() token (direct parameter)
option.SetDefaultValue(val) option.DefaultValueFactory = _ => val
new Option<T>() then set Name new Option<T>("--name")

References