|
| 1 | +--- |
| 2 | +title: System.CommandLine beta5 breaking changes |
| 3 | +description: "Learn what breaking changes were introduced in beta5 and why." |
| 4 | +ms.date: 16/06/2025 |
| 5 | +no-loc: [System.CommandLine] |
| 6 | +helpviewer_keywords: |
| 7 | + - "command line interface" |
| 8 | + - "command line" |
| 9 | + - "System.CommandLine" |
| 10 | +ms.topic: how-to |
| 11 | +--- |
| 12 | + |
| 13 | +# Breaking changes in beta5 |
| 14 | + |
| 15 | +[!INCLUDE [scl-preview](../../../includes/scl-preview.md)] |
| 16 | + |
| 17 | +Our main focus for beta5 was to improve the API, by simplifying it, making it more consistent and coherent with [Framework design guidelines](../design-guidelines/index.md). This article describes the changes that you need to make to your code to use beta5. |
| 18 | + |
| 19 | +## Renaming |
| 20 | + |
| 21 | +For type names, we wanted to disambiguate from other single-word types to avoid name conflicts. We also renamed some properties to make them follow the [guidelines for property names](../design-guidelines/names-of-type-members.md#names-of-properties). The following table shows the old and new names: |
| 22 | + |
| 23 | +| Old name | New name | |
| 24 | +|-------------------------------------------------------------|----------------------------------------------------------------| |
| 25 | +| `System.CommandLine.Parsing.Parser` | `System.CommandLine.Parsing.CommandLineParser` | |
| 26 | +| `System.CommandLine.Parsing.OptionResult.IsImplicit` | `System.CommandLine.Parsing.OptionResult.Implicit` | |
| 27 | +| `System.CommandLine.Option.IsRequired` | `System.CommandLine.Option.Required` | |
| 28 | +| `System.CommandLine.Symbol.IsHidden` | `System.CommandLine.Symbol.Hidden` | |
| 29 | +| `System.CommandLine.Option.ArgumentHelpName` | `System.CommandLine.Option.HelpName` | |
| 30 | +| `System.CommandLine.Parsing.OptionResult.Token` | `System.CommandLine.Parsing.OptionResult.IdentifierToken` | |
| 31 | +| `System.CommandLine.Parsing.ParseResult.FindResultFor` | `System.CommandLine.Parsing.ParseResult.GetResult` | |
| 32 | +| `System.CommandLine.Parsing.SymbolResult.ErrorMessage` | `System.CommandLine.Parsing.SymbolResult.AddError` | |
| 33 | + |
| 34 | +In case of the `ErrorMessage` property, we changed the name to `AddError` and made it a method. The goal was to allow to report multiple errors for the same symbol, which is useful when you want to report validation errors. |
| 35 | + |
| 36 | +## Exposing mutable collections |
| 37 | + |
| 38 | +In beta5, we changed the API to expose mutable collections instead of `Add` methods and (sometimes) readonly collections. This allows you to not only add items or enumerate them, but also remove them. The following table shows the old method and new property names: |
| 39 | + |
| 40 | +| Old method name | New property | |
| 41 | +|-------------------------------------------------------------|----------------------------------------------------------------| |
| 42 | +| `System.CommandLine.Command.AddArgument` | `System.CommandLine.Command.Arguments` | |
| 43 | +| `System.CommandLine.Command.AddOption` | `System.CommandLine.Command.Options` | |
| 44 | +| `System.CommandLine.Command.AddCommand` | `System.CommandLine.Command.Subcommands` | |
| 45 | +| `System.CommandLine.Command.AddValidator` | `System.CommandLine.Command.Validators` | |
| 46 | +| `System.CommandLine.Option.AddValidator` | `System.CommandLine.Option.Validators` | |
| 47 | +| `System.CommandLine.Argument.AddValidator` | `System.CommandLine.Argument.Validators` | |
| 48 | +| `System.CommandLine.Command.AddCompletions` | `System.CommandLine.Command.CompletionSources` | |
| 49 | +| `System.CommandLine.Option.AddCompletions` | `System.CommandLine.Option.CompletionSources` | |
| 50 | +| `System.CommandLine.Argument.AddCompletions` | `System.CommandLine.Argument.CompletionSources` | |
| 51 | +| `System.CommandLine.Command.AddAlias` | `System.CommandLine.Command.Aliases` | |
| 52 | +| `System.CommandLine.Option.AddAlias` | `System.CommandLine.Option.Aliases` | |
| 53 | + |
| 54 | +## Names and aliases |
| 55 | + |
| 56 | +Before beta5, the `System.CommandLine.Parsing.IdentifierSymbol` class was used to represent a base class for symbols that could be identified by a name and/or aliases (`Option` and `Command`). In beta5, every `Symbol` must provide a name, and only `Option` and `Command` support aliases. As a result, we made the following changes: |
| 57 | + |
| 58 | +- `name` is now a mandatory argument for every public constructor of `Argument`, `Option`, and `Command`. In case of `Argument`, it is not used for parsing, but to generate the help and completions text. In case of `Option` and `Command`, it is used to identify the symbol during parsing and also for help and completions. |
| 59 | +- `Symbol.Name` is no longer `virtual`, it's also readonly now and it returns the name as it was provided when the symbol was created. Because of that, `Symbol.DefaultName` was removed and `Option.Name` no longer removes the `--`, `-` or `/` or any other prefix from the longest alias. |
| 60 | +- The `Aliases` property exposed by `Option` and `Command` is now exposing a mutable collection. This collection no longer includes the name of the symbol. |
| 61 | +- `System.CommandLine.Parsing.IdentifierSymbol` was removed. |
| 62 | + |
| 63 | +Having the name always present allows for [getting the parsed value by name](parse-and-invoke.md#getvalue): |
| 64 | + |
| 65 | +```csharp |
| 66 | +RootCommand command = new("The description.") |
| 67 | +{ |
| 68 | + new Option<int>("--number") |
| 69 | +}; |
| 70 | + |
| 71 | +ParseResult parseResult = command.Parse(args); |
| 72 | +int number = parseResult.GetValue<int>("--number"); |
| 73 | +``` |
| 74 | + |
| 75 | +### Creating options with aliases |
| 76 | + |
| 77 | +We expect that most of the time, you will want to create options with aliases. In beta5, you can do this by using the `Option<T>` constructor that takes a name and `params` array of aliases. |
| 78 | + |
| 79 | +In the past, `Option<T>` had a constructor that took a name and a description. Because of that, the second argument might now be treated as an alias rather than a description. It's the only known breaking change in the API that is not going to cause a compiler error. |
| 80 | + |
| 81 | +Old code that used the constructor with a description should be updated to use the new constructor that takes a name and aliases, and then set the `Description` property separately. For example: |
| 82 | + |
| 83 | +```csharp |
| 84 | +Option<bool> beta4 = new("--help", "An option with aliases."); |
| 85 | +beta4b.Aliases.Add("-h"); |
| 86 | +beta4b.Aliases.Add("/h"); |
| 87 | + |
| 88 | +Option<bool> beta5 = new("--help", "-h", "/h") |
| 89 | +{ |
| 90 | + Description = "An option with aliases." |
| 91 | +}; |
| 92 | +``` |
| 93 | + |
| 94 | +## Default values |
| 95 | + |
| 96 | +In beta4, users could set default values for options and arguments by using the `SetDefaultValue` methods. Those methods were accepting an `object` value, which was not type-safe and could lead to runtime errors if the value was not compatible with the option or argument type: |
| 97 | + |
| 98 | +```csharp |
| 99 | +Option<int> option = new("--number"); |
| 100 | +option.SetDefaultValue("text"); // This is not type-safe, as the value is a string, not an int. |
| 101 | +``` |
| 102 | + |
| 103 | +Moreover, some of the `Option` and `Argument` constructors accepted a parse delegate and a boolean indicating whether the delegate was a custom parser or a default value provider. This was confusing. |
| 104 | + |
| 105 | +`Option<T>` and `Argument<T>` classes now have a `DefaultValueFactory` property that can be used to set the default value for the symbol. This property is invoked when the symbol is not provided in the command line input. |
| 106 | + |
| 107 | +```csharp |
| 108 | +Option<int> number = new("--number") |
| 109 | +{ |
| 110 | + DefaultValueFactory = _ => 42 |
| 111 | +}; |
| 112 | +``` |
| 113 | + |
| 114 | +`Argument<T>` and `Option<T>` also come with a `CustomParser` property that can be used to set a custom parser for the symbol: |
| 115 | + |
| 116 | +```csharp |
| 117 | +Argument<Uri> uri = new("arg") |
| 118 | +{ |
| 119 | + CustomParser = result => |
| 120 | + { |
| 121 | + if (!Uri.TryCreate(result.Tokens.Single().Value, UriKind.RelativeOrAbsolute, out var uriValue)) |
| 122 | + { |
| 123 | + result.AddError("Invalid URI format."); |
| 124 | + return null; |
| 125 | + } |
| 126 | + |
| 127 | + return uriValue; |
| 128 | + } |
| 129 | +}; |
| 130 | +``` |
| 131 | + |
| 132 | +Moreover, `CustomParser` accepts `Func<ParseResult, T>` delegate, rather than dedicated `ParseArgument` delegate (it got removed). |
| 133 | + |
| 134 | +For more examples of how to use `DefaultValueFactory` and `CustomParser`, see the [How to customize parsing and validation in System.CommandLine](parsing-and-validation.md) document. |
| 135 | + |
| 136 | +## The benefits of the simplified API |
| 137 | + |
| 138 | +PERF |
| 139 | +AOT |
| 140 | + |
| 141 | +## See also |
| 142 | + |
| 143 | +- [System.CommandLine overview](index.md) |
0 commit comments