Von Bedeutung
System.CommandLine
は現在プレビュー段階であり、このドキュメントはバージョン 2.0 ベータ 5 用です。
一部の情報は、リリース前に大幅に変更される可能性があるプレリリース製品に関連しています。 Microsoft は、ここで提供される情報に関して明示的または黙示的な保証を行いません。
2.0.0-beta5 リリースの主な焦点は、API を改善し、安定したバージョンの System.CommandLineのリリースに向けた一歩を踏み出すというものでした。 API は簡素化され、 フレームワークの設計ガイドラインとの一貫性が高まります。 この記事では、2.0.0-beta5 で行われた破壊的変更とその背後にある理由について説明します。
名前の変更
2.0.0-beta4 では、すべての型とメンバーが 名前付けガイドラインに従ったわけではありません。 一部は、ブール型プロパティに Is
プレフィックスを使用するなど、名前付け規則と一致していません。 2.0.0-beta5 では、一部の型とメンバーの名前が変更されました。 次の表は、古い名前と新しい名前を示しています。
前の名前 | 新しい名前 |
---|---|
System.CommandLine.Parsing.Parser |
System.CommandLine.Parsing.CommandLineParser |
System.CommandLine.Parsing.OptionResult.IsImplicit |
System.CommandLine.Parsing.OptionResult.Implicit |
System.CommandLine.Option.IsRequired |
System.CommandLine.Option.Required |
System.CommandLine.Symbol.IsHidden |
System.CommandLine.Symbol.Hidden |
System.CommandLine.Option.ArgumentHelpName |
System.CommandLine.Option.HelpName |
System.CommandLine.Parsing.OptionResult.Token |
System.CommandLine.Parsing.OptionResult.IdentifierToken |
System.CommandLine.Parsing.ParseResult.FindResultFor |
System.CommandLine.Parsing.ParseResult.GetResult |
System.CommandLine.Parsing.SymbolResult.ErrorMessage |
System.CommandLine.Parsing.SymbolResult.AddError |
同じシンボルに対して複数のエラーを報告できるようにするために、 ErrorMessage
プロパティはメソッドに変換され、名前が AddError
に変更されました。
変更可能なコレクションの公開
バージョン 2.0.0-beta4 には、引数、オプション、サブコマンド、検証コントロール、入力候補など、コレクションに項目を追加するために使用された多数の Add
メソッドがありました。 これらのコレクションの一部は、プロパティを介して読み取り専用コレクションとして公開されていました。 そのため、これらのコレクションから項目を削除することは不可能でした。
2.0.0-beta5 では、 Add
メソッドや (場合によっては) 読み取り専用コレクションではなく、変更可能なコレクションを公開するように API を変更しました。 これにより、アイテムを追加したり列挙したりするだけでなく、アイテムを削除することもできます。 次の表に、古いメソッドと新しいプロパティ名を示します。
古いメソッド名 | 新しいプロパティ |
---|---|
System.CommandLine.Command.AddArgument |
System.CommandLine.Command.Arguments.Add |
System.CommandLine.Command.AddOption |
System.CommandLine.Command.Options.Add |
System.CommandLine.Command.AddCommand |
System.CommandLine.Command.Subcommands.Add |
System.CommandLine.Command.AddValidator |
System.CommandLine.Command.Validators.Add |
System.CommandLine.Option.AddValidator |
System.CommandLine.Option.Validators.Add |
System.CommandLine.Argument.AddValidator |
System.CommandLine.Argument.Validators.Add |
System.CommandLine.Command.AddCompletions |
System.CommandLine.Command.CompletionSources.Add |
System.CommandLine.Option.AddCompletions |
System.CommandLine.Option.CompletionSources.Add |
System.CommandLine.Argument.AddCompletions |
System.CommandLine.Argument.CompletionSources.Add |
System.CommandLine.Command.AddAlias |
System.CommandLine.Command.Aliases.Add |
System.CommandLine.Option.AddAlias |
System.CommandLine.Option.Aliases.Add |
Aliases
プロパティが変更可能なコレクションになったので、RemoveAlias
メソッドとHasAlias
メソッドも削除されました。
Remove
メソッドを使用して、コレクションからエイリアスを削除できます。 エイリアスが存在するかどうかを確認するには、 Contains
メソッドを使用します。
名前とエイリアス
2.0.0-beta5 より前は、シンボルの名前と エイリアス の間に明確な分離がありませんでした。
Option<T>
コンストラクターにname
が指定されていない場合、シンボルはその名前を、--
、-
、削除などのプレフィックスを持つ最長のエイリアスとして報告/
。 それは混乱しました。
さらに、解析された値を取得するには、ユーザーはオプションまたは引数への参照を格納し、それを使用して ParseResult
から値を取得する必要がありました。
簡略化と明示的さを高めるために、シンボルの名前はすべてのシンボル コンストラクター ( Argument<T>
を含む) の必須パラメーターになりました。 また、名前とエイリアスの概念も分離しました。エイリアスは単なるエイリアスであり、シンボルの名前は含まれません。 もちろん、これらは省略可能です。 その結果、次の変更が行われました。
-
name
は、Argument<T>
、Option<T>
、およびCommand
のすべてのパブリック コンストラクターに対して必須の引数になりました。Argument<T>
の場合は、解析ではなく、ヘルプを生成するために使用されます。Option<T>
とCommand
の場合は、解析中にシンボルを識別し、ヘルプと完了のためにも使用されます。 -
Symbol.Name
プロパティはvirtual
なくなりました。現在は読み取り専用になり、シンボルの作成時に指定された名前が返されます。 そのため、Symbol.DefaultName
は削除され、Option.Name
は最長のエイリアスから--
、-
、または/
またはその他のプレフィックスを削除しなくなりました。 -
Option
およびCommand
によって公開されるAliases
プロパティが変更可能なコレクションになりました。 このコレクションには、シンボルの名前が含まれるようになりました。 -
System.CommandLine.Parsing.IdentifierSymbol
が削除されました (Command
とOption
の両方の基本型でした)。
常に名前を指定すると、 解析された値を名前で取得できます。
RootCommand command = new("The description.")
{
new Option<int>("--number")
};
ParseResult parseResult = command.Parse(args);
int number = parseResult.GetValue<int>("--number");
エイリアスを使用したオプションの作成
以前は、 Option<T>
多くのコンストラクターが公開され、その一部が名前を受け取りました。 この名前は必須になり、 Option<T>
に対してエイリアスが頻繁に提供されることが想定されているため、コンストラクターは 1 つだけです。 名前とエイリアスの params
配列を受け入れます。
2.0.0-beta5 より前の Option<T>
には、名前と説明を受け取るコンストラクターがありました。 そのため、2 番目の引数は説明ではなくエイリアスとして扱われる可能性があります。 これは、コンパイラ エラーを引き起こさない API の唯一の既知の破壊的変更です。
説明付きのコンストラクターを使用した古いコードは、名前とエイリアスを受け取る新しいコンストラクターを使用するように更新し、 Description
プロパティを個別に設定する必要があります。 例えば次が挙げられます。
Option<bool> beta4 = new("--help", "An option with aliases.");
beta4b.Aliases.Add("-h");
beta4b.Aliases.Add("/h");
Option<bool> beta5 = new("--help", "-h", "/h")
{
Description = "An option with aliases."
};
既定値とカスタム解析
2.0.0-beta4 では、ユーザーは SetDefaultValue
メソッドを使用してオプションと引数の既定値を設定できます。 これらのメソッドは、型セーフではなく、値がオプションまたは引数の型と互換性がない場合に実行時エラーにつながる可能性がある object
値を受け取りました。
Option<int> option = new("--number");
option.SetDefaultValue("text"); // This is not type-safe, as the value is a string, not an int.
さらに、一部の Option
および Argument
コンストラクターは、解析デリゲートと、デリゲートがカスタム パーサーか既定値プロバイダーかを示すブール値を受け取りました。 これは混乱しました。
Option<T>
Argument<T>
クラスには、オプションまたは引数の既定値を取得するために呼び出すことができるデリゲートを設定するために使用できるDefaultValueFactory
プロパティが追加されました。 このデリゲートは、解析されたコマンド ライン入力でオプションまたは引数が見つからない場合に呼び出されます。
Option<int> number = new("--number")
{
DefaultValueFactory = _ => 42
};
Argument<T>
Option<T>
には、シンボルのカスタム パーサーを設定するために使用できるCustomParser
プロパティも用意されています。
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;
}
};
さらに、CustomParser
は、前のParseArgument
デリゲートではなく、Func<ParseResult, T>
型のデリゲートを受け入れます。 API を簡略化し、API によって公開される型の数を減らすために、これと他のいくつかのカスタム デリゲートが削除されました。これにより、JIT コンパイル中に費やされるスタートアップ時間が短縮されます。
DefaultValueFactory
とCustomParser
の使用方法の他の例については、「System.CommandLineで解析と検証をカスタマイズする方法」を参照してください。
解析と呼び出しの分離
2.0.0-beta4 では、コマンドの解析と呼び出しを分離することは可能でしたが、その方法はかなり不明でした。
Command
はParse
メソッドを公開しませんでしたが、Command
のParse
、Invoke
、およびInvokeAsync
拡張メソッドCommandExtensions
提供されています。 どの方法をいつ使用するかは明らかではないため、これは混乱を招くものでした。 API を簡略化するために、次の変更が行われました。
-
Command
では、ParseResult
オブジェクトを返すParse
メソッドが公開されるようになりました。 このメソッドは、コマンド ライン入力を解析し、解析操作の結果を返すために使用されます。 さらに、コマンドが呼び出されず、解析され、同期的な方法でのみ実行されていることが明らかになります。 -
ParseResult
では、コマンドの呼び出しに使用できるInvoke
メソッドとInvokeAsync
メソッドの両方が公開されるようになりました。 これにより、解析後にコマンドが呼び出され、同期呼び出しと非同期呼び出しの両方が可能であることが明確になります。 -
CommandExtensions
クラスは不要になったため、削除されました。
コンフィギュレーション
2.0.0-beta5 より前は、一部のパブリック Parse
メソッドでのみ解析をカスタマイズすることが可能でした。 2 つのパブリック コンストラクター (1 つはCommand
を受け入れ、もう 1 つはCommandLineConfiguration
を受け入れる) を公開するParser
クラスがありました。
CommandLineConfiguration
は不変であり、作成するには、 CommandLineBuilder
クラスによって公開されたビルダー パターンを使用する必要がありました。 API を簡略化するために、次の変更が行われました。
-
CommandLineConfiguration
が変更可能になり、CommandLineBuilder
が削除されました。 構成の作成は、CommandLineConfiguration
のインスタンスを作成し、カスタマイズするプロパティを設定するのと同じくらい簡単になりました。 さらに、構成の新しいインスタンスを作成することは、CommandLineBuilder
のUseDefaults
メソッドを呼び出すことと同じです。 - すべての
Parse
メソッドは、解析のカスタマイズに使用できる省略可能なCommandLineConfiguration
パラメーターを受け入れるようになりました。 指定されていない場合は、既定の構成が使用されます。 -
Parser
名前の競合を回避するために、他のパーサーの種類からあいまいさを解消するために、CommandLineParser
に名前が変更されました。 ステートレスであるため、静的メソッドのみを持つ静的クラスになりました。 2 つのParse
解析メソッドが公開されます。1 つはIReadOnlyList<string> args
を受け入れ、もう 1 つはstring args
を受け入れます。 後者はCommandLineParser.SplitCommandLine
(パブリック) を使用して、コマンド ライン入力を トークン に分割してから解析します。
CommandLineBuilderExtensions
も削除されました。 メソッドを新しい API にマップする方法を次に示します。
CancelOnProcessTermination
は ProcessTerminationTimeout と呼ばれるCommandLineConfiguration
のプロパティになりました。 既定では、2 秒のタイムアウトで有効になっています。 無効にするには、null
に設定します。EnableDirectives
、UseEnvironmentVariableDirective
、UseParseDirective
、およびUseSuggestDirective
が削除されました。 新しい ディレクティブ 型が導入され、 RootCommand でSystem.CommandLine.RootCommand.Directives
プロパティが公開されるようになりました。 このコレクションを使用して、ディレクティブを追加、削除、および反復処理できます。 Suggest ディレクティブ は既定で含まれています。 DiagramDirective やEnvironmentVariablesDirective
などの他のディレクティブを使用することもできます。EnableLegacyDoubleDashBehavior
が削除されました。 一致しないトークンはすべて ParseResult.UnmatchedTokens プロパティによって公開されるようになりました。EnablePosixBundling
が削除されました。 バンドルが既定で有効になりました。これを無効にするには、 CommandLineConfiguration.EnableBundling プロパティをfalse
に設定します。RegisterWithDotnetSuggest
は、コストの高い操作 (通常はアプリケーションの起動時) を実行する際に削除されました。 次に、コマンドをdotnet suggest
manually に登録する必要があります。UseExceptionHandler
が削除されました。 既定の例外ハンドラーが既定で有効になりました。これを無効にするには、 CommandLineConfiguration.EnableDefaultExceptionHandler プロパティをfalse
に設定します。 これは、try-catch ブロックでInvoke
またはInvokeAsync
メソッドをラップするだけで、カスタムの方法で例外を処理する場合に便利です。UseHelp
とUseVersion
が削除されました。 ヘルプとバージョンは、 HelpOption および VersionOption パブリック型によって公開されるようになりました。 これらはどちらも、 RootCommand で定義されているオプションに既定で含まれています。UseHelpBuilder
が削除されました。 ヘルプ出力をカスタマイズする方法の詳細については、「System.CommandLineでヘルプをカスタマイズする方法」を参照してください。AddMiddleware
が削除されました。 アプリケーションの起動速度が低下し、機能なしで表現できます。UseParseErrorReporting
とUseTypoCorrections
が削除されました。ParseResult
を呼び出すと、解析エラーが既定で報告されるようになりました。ParseResult.Action
プロパティによって公開されるParseErrorAction
を使用して構成できます。ParseResult result = rootCommand.Parse("myArgs", config); if (result.Action is ParseErrorAction parseError) { parseError.ShowTypoCorrections = true; parseError.ShowHelp = false; }
UseLocalizationResources
とLocalizationResources
が削除されました。 この機能は、System.CommandLine
に不足している翻訳を追加するために、主にdotnet
CLI によって使用されました。 これらの翻訳はすべて System.CommandLine 自体に移動されているため、この機能は必要なくなりました。 言語のサポートが不足している場合は、 問題を報告してください。UseTokenReplacer
が削除されました。 応答ファイル は既定で有効になっていますが、System.CommandLine.CommandLineConfiguration.ResponseFileTokenReplacer
プロパティをnull
に設定することで無効にできます。 応答ファイルの処理方法をカスタマイズするカスタム実装を提供することもできます。
最後に、 IConsole
と関連するすべてのインターフェイス (IStandardOut
、 IStandardError
、 IStandardIn
) が削除されました。
System.CommandLine.CommandLineConfiguration
では、Output
と Error
の 2 つのTextWriter
プロパティが公開されます。 これらは、テスト用の出力をキャプチャするために使用できるStringWriter
など、任意のTextWriter
インスタンスに設定できます。 私たちの動機は、より少ない型を公開し、既存の抽象化を再利用することでした。
祈祷
2.0.0-beta4 では、 ICommandHandler
インターフェイスは、解析されたコマンドの呼び出しに使用された Invoke
メソッドと InvokeAsync
メソッドを公開しました。 これにより、同期コードと非同期コードを簡単に混在させることができました。たとえば、コマンドの同期ハンドラーを定義し、非同期的に呼び出します ( デッドロックが発生する可能性があります)。 さらに、コマンドに対してのみハンドラーを定義できますが、オプション (ヘルプを表示するヘルプなど) やディレクティブには定義できませんでした。
新しい抽象基底クラス System.CommandLine.CommandLineAction
および 2 つの派生クラス ( System.CommandLine.SynchronousCommandLineAction
と System.CommandLine.AsynchronousCommandLineAction
) が導入されました。 前者は int
終了コードを返す同期アクションに使用され、後者は Task<int>
終了コードを返す非同期アクションに使用されます。
アクションを定義するために派生型を作成する必要はありません。
System.CommandLine.Command.SetAction
メソッドを使用して、コマンドのアクションを設定できます。 同期アクションは、 System.CommandLine.ParseResult
パラメーターを受け取り、 int
終了コードを返すデリゲート (または何も返さなければ、既定の 0
終了コードが返される) にすることができます。 非同期アクションは、 System.CommandLine.ParseResult
パラメーターと CancellationToken パラメーターを受け取り、 Task<int>
を返すデリゲート (または既定の終了コードを返す Task
) にすることができます。
rootCommand.SetAction(ParseResult parseResult =>
{
FileInfo parsedFile = parseResult.GetValue(fileOption);
ReadFile(parsedFile);
});
以前は、InvokeAsync
に渡されたCancellationToken
は、InvocationContext
のメソッドを介してハンドラーに公開されていました。
rootCommand.SetHandler(async (InvocationContext context) =>
{
string? urlOptionValue = context.ParseResult.GetValueForOption(urlOption);
var token = context.GetCancellationToken();
returnCode = await DoRootCommand(urlOptionValue, token);
});
ユーザーの大半は、このトークンを取得してさらに渡すことができませんでした。 非同期アクション CancellationToken
必須の引数を作成し、コンパイラがそれ以上渡されない場合に警告を生成するようにしました (CA2016)。
rootCommand.SetAction((ParseResult parseResult, CancellationToken token) =>
{
string? urlOptionValue = parseResult.GetValue(urlOption);
return DoRootCommandAsync(urlOptionValue, token);
});
これらの変更とその他の前述の変更の結果、 InvocationContext
クラスも削除されました。
ParseResult
はアクションに直接渡されるため、解析された値とオプションに直接アクセスできます。
これらの変更を要約するには、次の手順を実行します。
-
ICommandHandler
インターフェイスが削除されました。SynchronousCommandLineAction
とAsynchronousCommandLineAction
が導入されました。 -
Command.SetHandler
メソッドの名前がSetAction
に変更されました。 -
Command.Handler
プロパティの名前がCommand.Action
に変更されました。Option
はOption.Action
で拡張されました。 -
InvocationContext
が削除されました。ParseResult
がアクションに直接渡されるようになりました。
アクションの使用方法の詳細については、「System.CommandLineでコマンドを解析して呼び出す方法」を参照してください。
簡略化された API の利点
2.0.0-beta5 で行われた変更により、API の一貫性が高く、将来性が高く、既存および新規のユーザーにとって使いやすくなることを期待しています。
パブリック インターフェイスの数が 11 から 0 に減少し、パブリック クラス (および構造体) が 56 から 38 に減ったので、新しいユーザーはより少ない概念と型を学習する必要があります。 パブリック メソッドの数は 378 から 235 に、パブリック プロパティは 118 から 99 に減少しました。
System.CommandLineによって参照されるアセンブリの数が 11 から 6 に減りました。
System.Collections
- System.Collections.Concurrent
- System.ComponentModel
System.Console
- System.Diagnostics.Process
System.Linq
System.Memory
- System.Net.Primitives
System.Runtime
- System.Runtime.Serialization.Formatters
+ System.Runtime.InteropServices
- System.Threading
これにより、ライブラリのサイズを 32%、次の NativeAOT アプリのサイズを 20%減らすことができます。
Option<bool> boolOption = new Option<bool>(new[] { "--bool", "-b" }, "Bool option");
Option<string> stringOption = new Option<string>(new[] { "--string", "-s" }, "String option");
RootCommand command = new RootCommand
{
boolOption,
stringOption
};
command.SetHandler<bool, string>(Run, boolOption, stringOption);
return new CommandLineBuilder(command).UseDefaults().Build().Invoke(args);
static void Run(bool boolean, string text)
{
Console.WriteLine($"Bool option: {text}");
Console.WriteLine($"String option: {boolean}");
}
Option<bool> boolOption = new Option<bool>("--bool", "-b") { Description = "Bool option" };
Option<string> stringOption = new Option<string>("--string", "-s") { Description = "String option" };
RootCommand command = new ()
{
boolOption,
stringOption,
};
command.SetAction(parseResult => Run(parseResult.GetValue(boolOption), parseResult.GetValue(stringOption)));
return new CommandLineConfiguration(command).Invoke(args);
static void Run(bool boolean, string text)
{
Console.WriteLine($"Bool option: {text}");
Console.WriteLine($"String option: {boolean}");
}
シンプルさによって、ライブラリのパフォーマンスも向上しました (主な目的ではなく、作業の副作用です)。 ベンチマークは、コマンドの解析と呼び出しが、2.0.0-beta4 よりも高速になったことを示しています。特に、多くのオプションと引数を持つ大規模なコマンドの場合です。 パフォーマンスの向上は、同期シナリオと非同期シナリオの両方で確認できます。
前に示した最も単純なアプリでは、次の結果が得られます。
BenchmarkDotNet v0.15.0, Windows 11 (10.0.26100.4061/24H2/2024Update/HudsonValley)
AMD Ryzen Threadripper PRO 3945WX 12-Cores 3.99GHz, 1 CPU, 24 logical and 12 physical cores
.NET SDK 9.0.300
[Host] : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2
Job-JJVAFK : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2
EvaluateOverhead=False OutlierMode=DontRemove InvocationCount=1
IterationCount=100 UnrollFactor=1 WarmupCount=3
| Method | Args | Mean | StdDev | Ratio |
|------------------------ |--------------- |----------:|---------:|------:|
| Empty | --bool -s test | 63.58 ms | 0.825 ms | 0.83 |
| EmptyAOT | --bool -s test | 14.39 ms | 0.507 ms | 0.19 |
| SystemCommandLineBeta4 | --bool -s test | 85.80 ms | 1.007 ms | 1.12 |
| SystemCommandLineNow | --bool -s test | 76.74 ms | 1.099 ms | 1.00 |
| SystemCommandLineNowR2R | --bool -s test | 69.35 ms | 1.127 ms | 0.90 |
| SystemCommandLineNowAOT | --bool -s test | 17.35 ms | 0.487 ms | 0.23 |
ご覧のように、スタートアップ時間 (特定の実行可能ファイルの実行に必要な時間を報告するベンチマーク) は、2.0.0-beta4 と比較して 12% 向上しています。 NativeAOT を使用してアプリをコンパイルすると、引数をまったく解析しない NativeAOT アプリより 3 ミリ秒遅くなります (上の表の EmptyAOT)。 また、空のアプリのオーバーヘッド (63.58 ミリ秒) を除外すると、解析は 2.0.0-beta4 (22.22 ミリ秒と 13.66 ミリ秒) よりも 40% 高速になります。
こちらも参照ください
.NET