Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Hlavním cílem verze 2.0.0-beta5 bylo zlepšit rozhraní API a podniknout krok směrem k vydání stabilní verze System.CommandLine. Rozhraní API byla zjednodušena a učiněna konzistentnější s pokyny pro návrh rámce. Tento článek popisuje zásadní změny provedené ve verzi 2.0.0-beta5 a 2.0.0-beta7 a odůvodnění za nimi.
Přejmenování
Ve verzi 2.0.0-beta4 ne všechny typy a členy dodržovaly pokyny pro pojmenování. Některé nebyly konzistentní s těmi konvenčními pravidly pojmenování, jako je použití Is předpony pro booleanové vlastnosti. Ve verzi 2.0.0-beta5 byly některé typy a členy přejmenovány. V následující tabulce jsou uvedeny staré a nové názvy:
| Starý název | Nový název |
|---|---|
System.CommandLine.Parsing.Parser |
CommandLineParser |
System.CommandLine.Parsing.OptionResult.IsImplicit |
Implicit |
System.CommandLine.Option.IsRequired |
Required |
System.CommandLine.Symbol.IsHidden |
Hidden |
System.CommandLine.Option.ArgumentHelpName |
HelpName |
System.CommandLine.Parsing.OptionResult.Token |
IdentifierToken |
System.CommandLine.Parsing.ParseResult.FindResultFor |
GetResult |
System.CommandLine.Parsing.SymbolResult.ErrorMessage |
AddError(String)† |
Aby bylo možné nahlásit více chyb pro stejný symbol ErrorMessage, byla vlastnost převedena na metodu a přejmenována na AddError.
Proměnlivé kolekce možností a validátorů
Verze 2.0.0-beta4 obsahovala řadu Add metod, které byly použity k přidání položek do kolekcí, jako jsou argumenty, možnosti, dílčí příkazy, validátory a dokončování. Některé z těchto kolekcí byly zpřístupněny prostřednictvím vlastností jako kolekce jen pro čtení. Z tohoto důvodu nebylo možné odebrat položky z těchto kolekcí.
Ve verzi 2.0.0-beta5 se rozhraní API změnila tak, aby zpřístupnila proměnlivé kolekce místo Add metod a (někdy) kolekcí jen pro čtení. To vám umožní nejen přidávat položky nebo je vyčíslit, ale také je odebrat. Následující tabulka ukazuje starou metodu a názvy nových vlastností:
| Starý název metody | Nová vlastnost |
|---|---|
Command.AddArgument |
Command.Arguments.Add |
Command.AddOption |
Command.Options.Add |
Command.AddCommand |
Command.Subcommands.Add |
Command.AddValidator |
Command.Validators.Add |
Option.AddValidator |
Option.Validators.Add |
Argument.AddValidator |
Argument.Validators.Add |
Command.AddCompletions |
Command.CompletionSources.Add |
Option.AddCompletions |
Option.CompletionSources.Add |
Argument.AddCompletions |
Argument.CompletionSources.Add |
Command.AddAlias |
Command.Aliases.Add |
Option.AddAlias |
Option.Aliases.Add |
Metody RemoveAlias a HasAlias byly také odebrány, protože vlastnost Aliases je nyní mutabilní kolekcí. Pomocí Remove této metody můžete z kolekce odebrat alias.
Contains Pomocí této metody zkontrolujte, jestli alias existuje.
Názvy a aliasy
Před verzí 2.0.0-beta5 nebylo jasné oddělení mezi názvem a aliasy symbolu. Pokud name nebyl poskytnut pro konstruktor Option<T>, symbol nahlásil svůj název jako nejdelší alias, který měl odstraněné předpony jako --, - nebo /. To bylo matoucí.
Kromě toho, abyste získali parsovanou hodnotu, museli jste uložit odkaz na možnost nebo argument a pak ho použít k získání hodnoty z ParseResult.
Pro zvýšení jednoduchosti a explicitnosti je teď název symbolu povinným parametrem pro každý konstruktor symbolu (včetně Argument<T>). Koncept názvu a aliasů je teď samostatný: aliasy jsou jenom aliasy a nezahrnují název symbolu. Samozřejmě, že jsou volitelné. V důsledku toho byly provedeny následující změny:
-
nameje nyní povinný argument pro každý veřejný konstruktor Argument<T>, Option<T>a Command. V případěArgument<T>se nepoužívá k analýze, ale k generování nápovědy. V případěOption<T>aCommand) se používá k identifikaci symbolu během analýzy a také k nápovědě a dokončování. - Vlastnost Symbol.Name již
virtualnení ; je nyní určena jen pro čtení a vrací název, který byl poskytnut při vytvoření symbolu. Z tohoto důvoduSymbol.DefaultNamebyla odebrána a Symbol.Name již neodebere--,-ani/žádnou jinou předponu z nejdelšího aliasu. - Vlastnost
Aliases, kterou zprostředkovávajíOptionaCommand, je nyní měnitelnou kolekcí. Tato kolekce už neobsahuje název symbolu. -
System.CommandLine.Parsing.IdentifierSymbolbyl odebrán (byl základní typ pro obaCommandiOption).
Když budete mít název vždy k dispozici, získáte parsovanou hodnotu podle názvu:
RootCommand command = new("The description.")
{
new Option<int>("--number")
};
ParseResult parseResult = command.Parse(args);
int number = parseResult.GetValue<int>("--number");
Vytváření možností pomocí aliasů
V minulosti Option<T> jsme odhalili mnoho konstruktorů, z nichž některé název přijali. Vzhledem k tomu, že název je teď povinný a aliasy budou často poskytovány pro Option<T>, je k dispozici pouze jeden konstruktor. Přijímá název a params pole aliasů.
Před verzí 2.0.0-beta5 měl Option<T> konstruktor, který přijímal název a popis. Z tohoto důvodu může být druhý argument nyní považován za alias, nikoli jako popis. Jedná se o jedinou známou zásadní změnu v rozhraní API, která nezpůsobuje chybu kompilátoru.
Aktualizujte veškerý kód, který předal popis jako argument konstruktoru, aby používal nový konstruktor, jenž přijímá název a aliasy, a poté nastavte vlastnost Description samostatně. Například:
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."
};
Výchozí hodnoty a vlastní analýza
Ve verzi 2.0.0-beta4 můžete pomocí metod nastavit výchozí hodnoty pro možnosti a argumenty SetDefaultValue . Tyto metody přijali object hodnotu, která nebyla typu bezpečná a mohla by vést k chybám za běhu, pokud hodnota nebyla kompatibilní s možností nebo typem argumentu:
Option<int> option = new("--number");
// This is not type safe, as the value is a string, not an int:
option.SetDefaultValue("text");
Některé z konstruktorů Option a Argument navíc přijímaly parse delegáta (parse) a logickou hodnotu (isDefault), která označuje, zda byl delegát vlastním analyzátorem nebo výchozím poskytovatelem hodnot, což bylo matoucí.
Třídy Option<T> a Argument<T> teď mají vlastnost DefaultValueFactory, kterou můžete použít k nastavení delegáta, který může být volán pro získání výchozí hodnoty pro volbu nebo argument. Tento delegát se vyvolá, když se možnost nebo argument nenajde ve vstupu parsovaného příkazového řádku.
Option<int> number = new("--number")
{
DefaultValueFactory = _ => 42
};
Argument<T> a Option<T> jsou také vybaveny vlastností CustomParser, kterou můžete použít k nastavení vlastního analyzátoru pro symbol.
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;
}
};
Kromě toho CustomParser přijímá delegáta typu Func<ParseResult,T>místo předchozího ParseArgument delegáta. Tento a několik dalších vlastních delegátů bylo odebráno, aby se zjednodušilo rozhraní API a snížil počet typů vystavených rozhraním API, což zkracuje dobu spuštění strávenou během kompilace JIT.
Další příklady použití DefaultValueFactory a CustomParser, naleznete v tématu Jak přizpůsobit parsování a ověřování v System.CommandLine.
Oddělení analýzy a vyvolání
Ve verzi 2.0.0-beta4 bylo možné oddělit parsování a vyvolání příkazů, ale nebylo jasné, jak to udělat.
Command nezpřístupnila metodu Parse, ale CommandExtensions poskytla rozšiřující metody Parse, Invoke, a InvokeAsync pro Command. To bylo matoucí, protože nebylo jasné, kterou metodu použít a kdy. Pro zjednodušení rozhraní API byly provedeny následující změny:
-
Command nyní zveřejňuje metodu
ParseParseResult, která vrací objekt. Tato metoda se používá k analýze vstupu příkazového řádku a vrácení výsledku operace analýzy. Kromě toho je zřejmé, že příkaz není vyvolán, ale analyzován, a to pouze synchronním způsobem. -
ParseResultnyní zveřejňuje oběInvokemetody aInvokeAsyncmetody, které můžete použít k vyvolání příkazu. Tento model jasně vysvětluje, že se příkaz vyvolá po analýze a umožňuje synchronní i asynchronní vyvolání. - Třída
CommandExtensionsbyla odebrána, protože už ji nepotřebujete.
Konfigurace
Před verzí 2.0.0-beta5 bylo možné přizpůsobit analýzu, ale pouze s některými veřejnými Parse metodami. Byla tam třída, která zveřejnila dva veřejné konstruktory: jeden přijímající Parser a druhý přijímající Command.
CommandLineConfiguration byl neměnný a k jeho vytvoření jste museli použít vzor builder poskytovaný třídou CommandLineBuilder. Pro zjednodušení rozhraní API byly provedeny následující změny:
-
CommandLineConfigurationbyla rozdělena do dvou proměnlivých tříd (v 2.0.0-beta7): ParserConfiguration a InvocationConfiguration. Vytvoření konfigurace vyvolání je teď stejně jednoduché jako vytvoření instanceInvocationConfigurationa nastavení vlastností, které chcete přizpůsobit. - Každá
Parsemetoda teď přijímá volitelný ParserConfiguration parametr, který můžete použít k přizpůsobení analýzy. Pokud není k dispozici, použije se výchozí konfigurace. - Aby se předešlo konfliktním názvům,
Parserbylo přejmenováno na CommandLineParser pro rozlišení od ostatních typů analyzátorů. Vzhledem k tomu, že je bezstavová, je teď statickou třídou pouze se statickými metodami. Zpřístupňuje dvěParsemetody analýzy: jedna přijímáIReadOnlyList<string> argsa druhá přijímástring args. Druhý příkaz používá CommandLineParser.SplitCommandLine(String) (také veřejné) k rozdělení vstupu příkazového řádku na tokeny před jeho parsováním.
CommandLineBuilderExtensions bylo také odebráno. Tady je postup, jak můžete mapovat jeho metody na nová rozhraní API:
CancelOnProcessTerminationje nyní vlastností InvocationConfiguration nazvanou ProcessTerminationTimeout. Ve výchozím nastavení je povolená se 2sekundovým vypršením časového limitu. Pokud ho chcete zakázat, nastavte ho nanull. Další informace naleznete v tématu Vypršení časového limitu ukončení procesu.EnableDirectives, aUseEnvironmentVariableDirectiveUseParseDirectiveUseSuggestDirectivebyly odebrány. Byl zaveden nový typ direktivy a RootCommand nyní zveřejňuje Directives vlastnost. Pomocí této kolekce můžete přidávat, odebírat a iterovat direktivy. Direktiva návrhu je ve výchozím nastavení zahrnuta; můžete také použít jiné direktivy, jako třeba DiagramDirective nebo EnvironmentVariablesDirective.EnableLegacyDoubleDashBehaviorbylo odebráno. Všechny neodpovídající tokeny jsou nyní zpřístupněny vlastností ParseResult.UnmatchedTokens. Další informace najdete v tématu Chybějící tokeny.EnablePosixBundlingbylo odebráno. Sdružování je ve výchozím nastavení teď povolené, můžete ho zakázat nastavením vlastnosti na ParserConfiguration.EnablePosixBundling hodnotu. Další informace naleznete v tématu EnablePosixBundling.RegisterWithDotnetSuggestbyla odebrána, protože prováděla nákladnou operaci, obvykle během spouštění aplikace. Teď musítedotnet suggest.UseExceptionHandlerbylo odebráno. Výchozí obslužná rutina výjimky je nyní povolena automaticky; Můžete ji InvocationConfiguration.EnableDefaultExceptionHandler zakázat nastavením vlastnosti nafalsehodnotu. To je užitečné, když chcete zpracovávat výjimky vlastním způsobem tak, že pouze zabalíteInvokeneboInvokeAsyncmetody do bloku try-catch. Další informace naleznete v tématu EnableDefaultExceptionHandler.UseHelpaUseVersionbyly odebrány. Nápověda a verze jsou nyní zpřístupněny prostřednictvím veřejných typů HelpOption a VersionOption. Obě jsou ve výchozím nastavení zahrnuté v možnostech definovaných uživatelem RootCommand. Další informace naleznete v částech Přizpůsobení výstupu nápovědy a Možnost verze.UseHelpBuilderbylo odebráno. Další informace o tom, jak přizpůsobit výstup nápovědy, naleznete v tématu Jak přizpůsobit nápovědu v System.CommandLine.AddMiddlewarebylo odebráno. Zpomalilo spuštění aplikace a funkce se dají vyjádřit bez ní.UseParseErrorReportingaUseTypoCorrectionsbyly odebrány. Při vyvoláníParseResultse chyby analýzy nyní oznamují ve výchozím nastavení. Můžete ho nakonfigurovat pomocí ParseErrorAction akce vystavené vlastností ParseResult.Action .ParseResult result = rootCommand.Parse("myArgs", config); if (result.Action is ParseErrorAction parseError) { parseError.ShowTypoCorrections = true; parseError.ShowHelp = false; }UseLocalizationResourcesaLocalizationResourcesbyly odebrány. Tuto funkci nejčastěji používalo příkazové rozhranídotnetk doplnění chybějících překladů doSystem.CommandLine. Všechny tyto překlady byly přesunuty do samotného objektu System.CommandLine , takže tato funkce už není nutná. Pokud chybí podpora vašeho jazyka, nahlašte problém.UseTokenReplacerbylo odebráno. Soubory odpovědí jsou ve výchozím nastavení povolené, ale můžete je zakázat nastavením vlastnosti ResponseFileTokenReplacer na hodnotunull. Můžete také poskytnout vlastní implementaci pro přizpůsobení způsobu zpracování souborů odpovědí.
V neposlední řadě byly IConsole a všechna související rozhraní (IStandardOut, IStandardError, IStandardIn) odebrány.
InvocationConfiguration zveřejňuje dvě TextWriter vlastnosti: Output a Error. Tyto vlastnosti můžete nastavit na libovolnou TextWriterStringWriterinstanci, například na instanci, kterou můžete použít k zachycení výstupu pro testování. Motivací této změny bylo zveřejnit méně typů a znovu použít existující abstrakce.
Vyvolání
Ve verzi 2.0.0-beta4 rozhraní ICommandHandler odhalovalo metody Invoke a InvokeAsync pro vyvolání analyzovaného příkazu. To usnadňuje kombinování synchronního a asynchronního kódu, například definováním synchronní obslužné rutiny pro příkaz a následným vyvoláním asynchronně (což může vést k zablokování). Kromě toho bylo možné definovat obslužnou rutinu pouze pro příkaz, ale ne pro možnost (například nápovědu, která zobrazuje nápovědu) nebo direktivu.
Byla zavedena nová abstraktní základní třída CommandLineAction a dvě odvozené třídy SynchronousCommandLineAction a AsynchronousCommandLineAction. První se používá pro synchronní akce, které vracejí int ukončovací kód, zatímco druhý se používá pro asynchronní akce, které vracejí Task<int> ukončovací kód.
K definování akce nemusíte vytvářet odvozený typ. Pomocí metody Command.SetAction můžete nastavit akci pro příkaz. Synchronní akcí může být delegát, který přijímá System.CommandLine.ParseResult parametr a vrací int ukončovací kód (nebo nic, a pak se vrátí výchozí 0 ukončovací kód). Asynchronní akce může být delegát, který přijímá parametry System.CommandLine.ParseResult a CancellationToken a vrací Task<int> (a nebo Task vrátí výchozí ukončovací kód).
rootCommand.SetAction(ParseResult parseResult =>
{
FileInfo parsedFile = parseResult.GetValue(fileOption);
ReadFile(parsedFile);
});
V minulosti byla CancellationToken předána InvokeAsync obslužnému programu prostřednictvím metody InvocationContext.
rootCommand.SetHandler(async (InvocationContext context) =>
{
string? urlOptionValue = context.ParseResult.GetValueForOption(urlOption);
var token = context.GetCancellationToken();
returnCode = await DoRootCommand(urlOptionValue, token);
});
Většina uživatelů tento token nezískávala a předávala ho dál.
CancellationToken je nyní povinný argument pro asynchronní akce, tak, aby kompilátor vytvořil varování, pokud není předán dále (viz CA2016).
rootCommand.SetAction((ParseResult parseResult, CancellationToken token) =>
{
string? urlOptionValue = parseResult.GetValue(urlOption);
return DoRootCommandAsync(urlOptionValue, token);
});
V důsledku těchto a dalších výše uvedených změn se InvocationContext třída také odebrala. Nyní je ParseResult předáno přímo akci, takže můžete z něj přímo získat přístup k analyzovaným hodnotám a volbám.
Shrnutí těchto změn:
- Rozhraní
ICommandHandlerbylo odebráno.SynchronousCommandLineActionaAsynchronousCommandLineActionbyly zavedeny. - Metoda
Command.SetHandlerbyla přejmenována na SetAction. - Vlastnost
Command.Handlerbyla přejmenována na Command.Action.Optionbyla rozšířena o Option.Action. -
InvocationContextbylo odebráno.ParseResultje nyní předán přímo akci.
Další podrobnosti o použití akcí naleznete v tématu Jak parsovat a vyvolat příkazy v System.CommandLine.
Výhody zjednodušeného rozhraní API
Změny zavedené ve verzi 2.0.0-beta5 činí rozhraní API konzistentnější, připravené na budoucnost a jednodušší k použití pro stávající i nové uživatele.
Noví uživatelé se musí naučit méně konceptů a typů, protože počet veřejných rozhraní se snížil z 11 na 0 a veřejné třídy (a struktury) se snížily z 56 na 38. Počet veřejných metod klesl z 378 na 235 a počet veřejných vlastností z 118 na 99.
Počet sestavení, na která System.CommandLine odkazuje, se sníží z 11 na 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
Velikost knihovny se zmenší (o 32 %) a také velikost aplikací NativeAOT, které tuto knihovnu používají, se zmenší.
Jednoduchost také zlepšila výkon knihovny (je to vedlejší účinek práce, nikoli hlavní cíl této knihovny). Srovnávací testy ukazují, že analýza a vyvolání příkazů je nyní rychlejší než ve verzi 2.0.0-beta4, zejména u velkých příkazů s mnoha možnostmi a argumenty. Vylepšení výkonu jsou viditelná v synchronních i asynchronních scénářích.
Nejjednodušší aplikace, která byla prezentována dříve, produkuje následující výsledky:
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 |
Jak vidíte, čas spuštění (srovnávací testy hlásí čas potřebný ke spuštění daného spustitelného souboru) se oproti 2.0.0-beta4 zlepšil o 12%. Pokud aplikaci zkompilujete pomocí NativeAOT, je to jen o 3 ms pomalejší než aplikace NativeAOT, která vůbec neanalyluje argy (EmptyAOT v tabulce výše). Pokud také vyloučíte režii prázdné aplikace (63,58 ms), analýza je 40% rychlejší než ve verzi 2.0.0-beta4 (22.22 ms vs 13.66 ms).