次の方法で共有


System.CommandLine 2.0.0-beta5 移行ガイド

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 が削除されました ( CommandOptionの両方の基本型でした)。

常に名前を指定すると、 解析された値を名前で取得できます。

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 コンパイル中に費やされるスタートアップ時間が短縮されます。

DefaultValueFactoryCustomParserの使用方法の他の例については、「System.CommandLineで解析と検証をカスタマイズする方法」を参照してください。

解析と呼び出しの分離

2.0.0-beta4 では、コマンドの解析と呼び出しを分離することは可能でしたが、その方法はかなり不明でした。 CommandParseメソッドを公開しませんでしたが、CommandParseInvoke、および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 のインスタンスを作成し、カスタマイズするプロパティを設定するのと同じくらい簡単になりました。 さらに、構成の新しいインスタンスを作成することは、 CommandLineBuilderUseDefaults メソッドを呼び出すことと同じです。
  • すべての Parse メソッドは、解析のカスタマイズに使用できる省略可能な CommandLineConfiguration パラメーターを受け入れるようになりました。 指定されていない場合は、既定の構成が使用されます。
  • Parser 名前の競合を回避するために、他のパーサーの種類からあいまいさを解消するために、 CommandLineParser に名前が変更されました。 ステートレスであるため、静的メソッドのみを持つ静的クラスになりました。 2 つの Parse 解析メソッドが公開されます。1 つは IReadOnlyList<string> args を受け入れ、もう 1 つは string argsを受け入れます。 後者は CommandLineParser.SplitCommandLine (パブリック) を使用して、コマンド ライン入力を トークン に分割してから解析します。

CommandLineBuilderExtensions も削除されました。 メソッドを新しい API にマップする方法を次に示します。

  • CancelOnProcessTerminationProcessTerminationTimeout と呼ばれるCommandLineConfigurationのプロパティになりました。 既定では、2 秒のタイムアウトで有効になっています。 無効にするには、 null に設定します。

  • EnableDirectivesUseEnvironmentVariableDirectiveUseParseDirective、および UseSuggestDirective が削除されました。 新しい ディレクティブ 型が導入され、 RootCommandSystem.CommandLine.RootCommand.Directives プロパティが公開されるようになりました。 このコレクションを使用して、ディレクティブを追加、削除、および反復処理できます。 Suggest ディレクティブ は既定で含まれています。 DiagramDirectiveEnvironmentVariablesDirective などの他のディレクティブを使用することもできます。

  • EnableLegacyDoubleDashBehavior が削除されました。 一致しないトークンはすべて ParseResult.UnmatchedTokens プロパティによって公開されるようになりました。

  • EnablePosixBundling が削除されました。 バンドルが既定で有効になりました。これを無効にするには、 CommandLineConfiguration.EnableBundling プロパティをfalse に設定します。

  • RegisterWithDotnetSuggest は、コストの高い操作 (通常はアプリケーションの起動時) を実行する際に削除されました。 次に、コマンドを dotnet suggestmanually に登録する必要があります。

  • UseExceptionHandler が削除されました。 既定の例外ハンドラーが既定で有効になりました。これを無効にするには、 CommandLineConfiguration.EnableDefaultExceptionHandler プロパティを falseに設定します。 これは、try-catch ブロックで Invoke または InvokeAsync メソッドをラップするだけで、カスタムの方法で例外を処理する場合に便利です。

  • UseHelpUseVersion が削除されました。 ヘルプとバージョンは、 HelpOption および VersionOption パブリック型によって公開されるようになりました。 これらはどちらも、 RootCommand で定義されているオプションに既定で含まれています。

  • UseHelpBuilder が削除されました。 ヘルプ出力をカスタマイズする方法の詳細については、「System.CommandLineでヘルプをカスタマイズする方法」を参照してください。

  • AddMiddleware が削除されました。 アプリケーションの起動速度が低下し、機能なしで表現できます。

  • UseParseErrorReportingUseTypoCorrections が削除されました。 ParseResultを呼び出すと、解析エラーが既定で報告されるようになりました。 ParseResult.Actionプロパティによって公開されるParseErrorActionを使用して構成できます。

    ParseResult result = rootCommand.Parse("myArgs", config);
    if (result.Action is ParseErrorAction parseError)
    {
        parseError.ShowTypoCorrections = true;
        parseError.ShowHelp = false;
    }
    
  • UseLocalizationResourcesLocalizationResources が削除されました。 この機能は、System.CommandLineに不足している翻訳を追加するために、主に dotnet CLI によって使用されました。 これらの翻訳はすべて System.CommandLine 自体に移動されているため、この機能は必要なくなりました。 言語のサポートが不足している場合は、 問題を報告してください。

  • UseTokenReplacer が削除されました。 応答ファイル は既定で有効になっていますが、 System.CommandLine.CommandLineConfiguration.ResponseFileTokenReplacer プロパティを null に設定することで無効にできます。 応答ファイルの処理方法をカスタマイズするカスタム実装を提供することもできます。

最後に、 IConsole と関連するすべてのインターフェイス (IStandardOutIStandardErrorIStandardIn) が削除されました。 System.CommandLine.CommandLineConfigurationでは、OutputError の 2 つのTextWriter プロパティが公開されます。 これらは、テスト用の出力をキャプチャするために使用できるStringWriterなど、任意のTextWriter インスタンスに設定できます。 私たちの動機は、より少ない型を公開し、既存の抽象化を再利用することでした。

祈祷

2.0.0-beta4 では、 ICommandHandler インターフェイスは、解析されたコマンドの呼び出しに使用された Invoke メソッドと InvokeAsync メソッドを公開しました。 これにより、同期コードと非同期コードを簡単に混在させることができました。たとえば、コマンドの同期ハンドラーを定義し、非同期的に呼び出します ( デッドロックが発生する可能性があります)。 さらに、コマンドに対してのみハンドラーを定義できますが、オプション (ヘルプを表示するヘルプなど) やディレクティブには定義できませんでした。

新しい抽象基底クラス System.CommandLine.CommandLineActionおよび 2 つの派生クラス ( System.CommandLine.SynchronousCommandLineActionSystem.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 インターフェイスが削除されました。 SynchronousCommandLineActionAsynchronousCommandLineAction が導入されました。
  • Command.SetHandler メソッドの名前が SetAction に変更されました。
  • Command.Handler プロパティの名前が Command.Action に変更されました。 OptionOption.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% 高速になります。

こちらも参照ください