這很重要
System.CommandLine
目前為預覽版,這份文件適用於 2.0 beta 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 中,我們已將 API 變更為公開可變動的集合,而不是 Add
方法和 (有時) 只讀集合。 這可讓您不僅新增專案或列舉專案,還可以移除這些專案。 下表顯示舊方法和新的屬性名稱:
舊方法名稱 | 新屬性 |
---|---|
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 |
RemoveAlias
和 HasAlias
方法也已移除,因為 Aliases
屬性現在是可變動的集合。 您可以使用 Remove
方法從集合中移除別名。
Contains
使用方法來檢查別名是否存在。
名稱和別名
在 2.0.0-beta5 之前,符號的名稱和 別名 之間沒有明確的分隔。 當未提供 name
作為 Option<T>
建構函式時,符號報告其名稱為最長的別名,並去除如 --
、-
或 /
之類的前綴。 這令人困惑。
此外,若要取得剖析的值,用戶必須儲存選項或自變數的參考,然後使用它從 ParseResult
取得值。
為了提升簡單性和明確性,符號的名稱現在是每個符號建構函式的必要參數(包括 Argument<T>
)。 我們也分隔了名稱和別名的概念:現在別名只是別名,而且不包含符號的名稱。 當然,它們是選擇性的。 因此,已進行下列變更:
-
name
現在是Argument<T>
、Option<T>
和Command
每個公用建構函式的必要參數。 在Argument<T>
的情形下,它不會用於解析,而是用來生成幫助信息。 在Option<T>
和Command
的情況下,它用來在剖析期間識別符號,也用於說明和完成。 - 屬性
Symbol.Name
已不再是virtual
;它現在是只讀的,而且會在建立符號時傳回它所提供的名稱。 因此,Symbol.DefaultName
已移除,且Option.Name
不再從最長的別名中移除--
、-
或/
任何其他前置詞。 -
Aliases
和Option
Command
所公開的屬性現在是可變動的集合。 此集合不再包含符號的名稱。 -
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>
提供別名,因此只有單一建構函式。 它會接受名稱和 params
別名陣列。
在 2.0.0-beta5 之前,一個名為Option<T>
的組件有一個建構子,它需要名稱和描述作為參數。 因此,第二個參數現在可能被視為別名,而不是描述。 這是 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
接受的是類型為 Func<ParseResult, T>
的委託,而不是先前 ParseArgument
的委託。 這和一些其他自定義委派已移除,以簡化 API 並減少 API 所公開的類型數目,這可減少 JIT 編譯期間所花費的啟動時間。
欲了解如何使用 DefaultValueFactory
和 CustomParser
的更多範例,請參閱 如何在 System.CommandLine 中自訂剖析和驗證。
剖析與調用的分離
在 2.0.0-beta4 中,可以分隔命令的剖析和叫用,但目前還不清楚該怎麼做。
Command
未公開 Parse
方法,但 CommandExtensions
提供 Parse
、Invoke
和 InvokeAsync
擴充方法給 Command
。 這令人困惑,因為目前還不清楚要使用哪一種方法,以及何時使用。 已進行下列變更以簡化 API:
-
Command
現在公開提供一個返回Parse
物件的方法。 這個方法可用來剖析命令行輸入,並傳回剖析作業的結果。 此外,它明確表示命令不會被叫用,而只會被同步方式分析。 -
ParseResult
現在公開可用來叫用命令的Invoke
方法和InvokeAsync
方法。 這可清楚指出命令會在剖析之後叫用,並允許同步和異步調用。 - 類別
CommandExtensions
已移除,因為不再需要類別。
設定
在 2.0.0-beta5 之前,您可以自定義剖析,但只能使用某些公用 Parse
方法。 有一個Parser
類別公開了兩個公用建構函式:一個接受Command
,另一個接受CommandLineConfiguration
。
CommandLineConfiguration
是不可變的,而且為了建立它,您必須使用 類別公開的 CommandLineBuilder
產生器模式。 已進行下列變更以簡化 API:
-
CommandLineConfiguration
已設為可變動且CommandLineBuilder
已移除。 建立組態現在就像建立 實例CommandLineConfiguration
一樣簡單,並設定您想要自定義的屬性。 此外,建立新的組態實例相當於呼叫CommandLineBuilder
的UseDefaults
方法。 -
Parse
方法現在都接受一個可以用來自訂剖析的可選CommandLineConfiguration
參數。 未提供時,會使用預設組態。 -
Parser
已重新命名為CommandLineParser
來釐清其他剖析器類型,以避免名稱衝突。 由於它是無狀態的,所以現在是只有靜態方法的靜態類別。 它公開了兩個Parse
剖析方法:一個接受IReadOnlyList<string> args
,另一個接受string args
。 後者使用CommandLineParser.SplitCommandLine
(也是公共的)將命令行輸入分割成 標記,然後進行分析。
CommandLineBuilderExtensions
也已移除。 以下說明如何將其方法對應至新的 API:
CancelOnProcessTermination
現在是名為CommandLineConfiguration
的 的屬性。 默認情況下會啟用,並設定 2 秒的逾時。 將它設定為null
以停用它。EnableDirectives
、UseEnvironmentVariableDirective
、UseParseDirective
和UseSuggestDirective
已移除。 引進了新的 指示詞 類型,而 RootCommand 現在會公開System.CommandLine.RootCommand.Directives
屬性。 您可以使用這個集合來新增、移除和迭代指令。 建議指示詞 預設會包含;您也可以使用其他指示詞,例如 DiagramDirective 或EnvironmentVariablesDirective
。EnableLegacyDoubleDashBehavior
已移除。 所有不相符的令牌現在都會由 ParseResult.UnmatchedTokens 屬性公開。EnablePosixBundling
已移除。 現在預設會啟用組合,您可以通過將 CommandLineConfiguration.EnableBundling 屬性設定為false
來停用此功能。RegisterWithDotnetSuggest
已移除,因為它執行昂貴的作業,通常是在應用程式啟動期間。 現在您必須dotnet suggest
註冊命令。UseExceptionHandler
已移除。 默認會啟用默認例外狀況處理程式,您可以將 CommandLineConfiguration.EnableDefaultExceptionHandler 屬性設定為false
來停用它。 當您想要以自定義方式處理例外狀況時,只要將Invoke
或InvokeAsync
方法包裝在 try-catch 區塊中,就會很有用。UseHelp
和UseVersion
已移除。 說明和版本現在會由 HelpOption 和 VersionOption 公用類型公開。 它們預設會包含在 RootCommand 所定義的選項中。UseHelpBuilder
已移除。 如需有關自定義說明輸出的更多資訊,請參閱 如何自定義在System.CommandLine中的說明。AddMiddleware
已移除。 它減緩了應用程式啟動的速度,而且不需要它即可表示功能。UseParseErrorReporting
和UseTypoCorrections
已移除。 呼叫ParseResult
時,預設會報告解析錯誤。 您可以使用ParseErrorAction
由ParseResult.Action
屬性公開來設定它。ParseResult result = rootCommand.Parse("myArgs", config); if (result.Action is ParseErrorAction parseError) { parseError.ShowTypoCorrections = true; parseError.ShowHelp = false; }
UseLocalizationResources
和LocalizationResources
已移除。 這項功能主要是由dotnet
CLI 用來將遺漏的翻譯新增至System.CommandLine
。 所有這些翻譯都已移至 System.CommandLine 本身,因此不再需要此功能。 如果我們缺少對語言的支援,請 回報問題。UseTokenReplacer
已移除。 默認會啟用回應檔,但您可以將 屬性設定System.CommandLine.CommandLineConfiguration.ResponseFileTokenReplacer
為null
來停用它們。 您也可以提供自定義實作,以自定義回應檔的處理方式。
最後但同樣重要的是,IConsole
和所有相關介面(IStandardOut
、IStandardError
、IStandardIn
)都已被移除。
System.CommandLine.CommandLineConfiguration
會公開兩個 TextWriter
屬性: Output
和 Error
。 這些可以設定為任何 TextWriter
實例,例如 StringWriter
,可用來擷取輸出進行測試。 我們的動機是公開較少的類型,並重複使用現有的抽象概念。
調用
在 2.0.0-beta4 中,ICommandHandler
介面公開了用來叫用剖析命令的 Invoke
和 InvokeAsync
方法。 這可讓您輕鬆地混合同步和異步程序代碼,例如,定義命令的同步處理程式,然後以異步方式叫用它(這可能會導致 死結)。 此外,只能針對命令定義處理程式,但不能針對選項定義處理程式(例如顯示幫助的幫助)或指令。
新的抽象基類 System.CommandLine.CommandLineAction
和兩個衍生類別: System.CommandLine.SynchronousCommandLineAction
和 System.CommandLine.AsynchronousCommandLineAction
已引進。 前者用於傳回結束代碼的 int
同步動作,而後者則用於傳回結束代碼的 Task<int>
異步動作。
您不需要建立衍生類型來定義動作。 您可以使用 System.CommandLine.Command.SetAction
方法來設定命令的動作。 同步動作可以是接受 System.CommandLine.ParseResult
參數並傳回結束代碼的委派函式(或者不返回任何內容,此時將返回默認 int
結束代碼)。 異步動作可以是接受 System.CommandLine.ParseResult
和 CancellationToken 參數並傳回 Task<int>
的委派(或傳回 Task
以獲取預設結束代碼)。
rootCommand.SetAction(ParseResult parseResult =>
{
FileInfo parsedFile = parseResult.GetValue(fileOption);
ReadFile(parsedFile);
});
過去,傳遞到 CancellationToken
的 InvokeAsync
會透過 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%,並將下列原生AOT應用程式的大小減少 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 編譯應用程式,其速度只會比不會剖析 args 的 NativeAOT 應用程式慢 3 毫秒(上表中的 EmptyAOT)。 此外,當我們排除空應用程式的額外負荷時(63.58 毫秒),剖析的速度比 2.0.0-beta4 快 40% 快(22.22 毫秒與 13.66 毫秒)。