Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
De belangrijkste focus voor de release 2.0.0-beta5 was het verbeteren van de API's en het nemen van een stap voor het vrijgeven van een stabiele versie van System.CommandLine. De API's zijn vereenvoudigd en coherenter en consistenter gemaakt met de ontwerprichtlijnen voor frameworks. In dit artikel worden de belangrijke wijzigingen beschreven die zijn aangebracht in 2.0.0-beta5 en 2.0.0-beta7 en de redenering erachter.
Hernoemen
In 2.0.0-beta4 hebben niet alle typen en leden de naamgevingsrichtlijnen gevolgd. Sommige waren niet consistent met de naamconventies, zoals het gebruik van het Is voorvoegsel voor Boole-eigenschappen. In 2.0.0-beta5 zijn sommige typen en leden hernoemd. In de volgende tabel ziet u de oude en nieuwe namen:
| Oude naam | Nieuwe naam |
|---|---|
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)† |
† Om toe te staan dat meerdere fouten voor hetzelfde symbool worden gerapporteerd, is de ErrorMessage eigenschap geconverteerd naar een methode en gewijzigd in AddError.
Onveranderbare verzamelingen opties en validators
Versie 2.0.0-beta4 had talloze Add methoden die werden gebruikt om items toe te voegen aan verzamelingen, zoals argumenten, opties, subopdrachten, validators en voltooiingen. Sommige van deze verzamelingen werden blootgesteld via eigenschappen als alleen-lezen verzamelingen. Daarom was het onmogelijk om items uit die verzamelingen te verwijderen.
In 2.0.0-beta5 zijn de API's gewijzigd om veranderlijke verzamelingen beschikbaar te maken in plaats van Add methoden en (soms) alleen-lezenverzamelingen. Hiermee kunt u niet alleen items toevoegen of opsommen, maar ook verwijderen. In de volgende tabel ziet u de oude methode en nieuwe eigenschapsnamen:
| Oude methodenaam | Nieuwe eigenschap |
|---|---|
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 |
De RemoveAlias en HasAlias methoden zijn ook verwijderd, omdat de Aliases eigenschap nu een onveranderbare verzameling is. U kunt de Remove methode gebruiken om een alias uit de verzameling te verwijderen. Gebruik de Contains methode om te controleren of er een alias bestaat.
Namen en aliassen
Vóór 2.0.0-beta5 was er geen duidelijke scheiding tussen de naam en aliassen van een symbool. Als name niet werd opgegeven voor de Option<T> constructor, rapporteerde het symbool zijn naam als de langste alias met voorvoegsels zoals --, -, of / verwijderd. Dat was verwarrend.
Om de geparseerde waarde op te halen, moest u bovendien een verwijzing naar een optie of argument opslaan en deze vervolgens gebruiken om de waarde op te halen uit ParseResult.
Ter bevordering van eenvoud en explicietheid is de naam van een symbool nu een verplichte parameter voor elke symboolconstructor (inclusief Argument<T>). Het concept van een naam en aliassen is nu gescheiden: aliassen zijn alleen aliassen en bevatten niet de naam van het symbool. Natuurlijk zijn ze optioneel. Als gevolg hiervan zijn de volgende wijzigingen aangebracht:
-
nameis nu een verplicht argument voor elke openbare constructor van Argument<T>, Option<T>en Command. In het geval vanArgument<T>, wordt het niet gebruikt voor parseren, maar om de help te genereren. In het geval vanOption<T>enCommandwordt het gebruikt om het symbool te identificeren tijdens het parseren en ook voor hulp en voltooiingen. - De Symbol.Name eigenschap is niet meer
virtual; deze is nu alleen-lezen en retourneert de naam zoals deze is opgegeven toen het symbool werd gemaakt. Daarom werdSymbol.DefaultNameverwijderd en verwijdert Symbol.Name niet langer het--,-,/of een ander voorvoegsel uit de langste alias. - De
Aliaseseigenschap die wordt weergegeven doorOptionenCommandis nu een onveranderbare verzameling. Deze verzameling bevat niet langer de naam van het symbool. -
System.CommandLine.Parsing.IdentifierSymbolis verwijderd (het was een basistype voor beideCommandenOption).
Als u de naam altijd aanwezig hebt, kunt u de geparseerde waarde op naam ophalen:
RootCommand command = new("The description.")
{
new Option<int>("--number")
};
ParseResult parseResult = command.Parse(args);
int number = parseResult.GetValue<int>("--number");
Opties maken met aliassen
In het verleden werden Option<T> veel constructors blootgelegd, waarvan sommige de naam accepteerden. Omdat de naam nu verplicht is en aliassen vaak worden opgegeven Option<T>, is er slechts één constructor. De naam en een params matrix met aliassen worden geaccepteerd.
Vóór 2.0.0-beta5 had Option<T> een constructor die een naam en een beschrijving ontving. Daarom kan het tweede argument nu worden behandeld als een alias in plaats van een beschrijving. Dit is de enige bekende wijziging die fouten veroorzaakt in de API die geen compilerfout veroorzaakt.
Werk alle code bij die een beschrijving heeft doorgegeven aan de constructor om de nieuwe constructor te gebruiken die een naam en aliassen gebruikt en stel de Description eigenschap vervolgens afzonderlijk in. Voorbeeld:
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."
};
Standaardwaarden en aangepaste parsering
In 2.0.0-beta4 kunt u standaardwaarden instellen voor opties en argumenten met behulp van de SetDefaultValue methoden. Deze methoden hebben een object waarde geaccepteerd, die niet veilig was en kan leiden tot runtimefouten als de waarde niet compatibel was met de optie of het argumenttype:
Option<int> option = new("--number");
// This is not type safe, as the value is a string, not an int:
option.SetDefaultValue("text");
Bovendien hebben sommige van de Option en Argument constructors een parseringsdelegatie (parse) en een Booleaanse waarde (isDefault) geaccepteerd die aangeeft of de gemachtigde een aangepaste parser of een standaardwaardeprovider was, wat verwarrend was.
Option<T> en Argument<T> klassen hebben nu een DefaultValueFactory eigenschap die u kunt gebruiken om een gemachtigde in te stellen die kan worden aangeroepen om de standaardwaarde voor de optie of het argument op te halen. Deze gemachtigde wordt aangeroepen wanneer de optie of het argument niet wordt gevonden in de geparseerde opdrachtregelinvoer.
Option<int> number = new("--number")
{
DefaultValueFactory = _ => 42
};
Argument<T> en Option<T> wordt ook geleverd met een CustomParser eigenschap die u kunt gebruiken om een aangepaste parser in te stellen voor het symbool:
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 Bovendien accepteert u een gemachtigde van het typeFunc<ParseResult,T>, in plaats van de vorige ParseArgument gedelegeerde. Deze en enkele andere aangepaste gemachtigden zijn verwijderd om de API te vereenvoudigen en het aantal typen te verminderen dat door de API wordt weergegeven, waardoor de opstarttijd tijdens JIT-compilatie wordt verminderd.
Voor meer voorbeelden van hoe u DefaultValueFactory en CustomParser kunt gebruiken, zie Hoe parsering en validatie aan te passen in System.CommandLine.
Scheiding van parseren en aanroepen
In 2.0.0-beta4 was het mogelijk om het parseren en aanroepen van opdrachten te scheiden, maar het was niet duidelijk hoe het moest.
Command heeft geen Parse methode blootgelegd, maar CommandExtensions bood wel Parse, Invoke, en InvokeAsync extensiemethoden voor Command. Dit was verwarrend, omdat het niet duidelijk was welke methode moest worden gebruikt en wanneer. De volgende wijzigingen zijn aangebracht om de API te vereenvoudigen:
-
Command maakt nu een
Parsemethode beschikbaar die eenParseResultobject retourneert. Deze methode wordt gebruikt om de opdrachtregelinvoer te parseren en het resultaat van de parseringsbewerking te retourneren. Bovendien wordt duidelijk dat de opdracht niet wordt aangeroepen, maar geparseerd, en alleen synchroon. -
ParseResultmaakt nu zowel alsInvokeInvokeAsyncmethoden beschikbaar die u kunt gebruiken om de opdracht aan te roepen. Dit patroon maakt het duidelijk dat de opdracht wordt aangeroepen na parseren en dat zowel synchrone als asynchrone aanroep mogelijk is. - De
CommandExtensionsklasse is verwijderd, omdat deze niet meer nodig is.
Configuratie
Vóór 2.0.0-beta5 was het mogelijk om de parsering aan te passen, maar alleen met een aantal openbare Parse methoden. Er was een Parser klasse die twee openbare constructors bood: één die een Command accepteert en een andere die een CommandLineConfiguration accepteert.
CommandLineConfiguration onveranderbaar was en om het te maken, moest u een opbouwpatroon gebruiken dat door de CommandLineBuilder klasse wordt weergegeven. De volgende wijzigingen zijn aangebracht om de API te vereenvoudigen:
-
CommandLineConfigurationis gesplitst in twee veranderlijke klassen (in 2.0.0-beta7): ParserConfiguration en InvocationConfiguration. Het maken van een aanroepconfiguratie is nu net zo eenvoudig als het maken van een exemplaar vanInvocationConfigurationen het instellen van de eigenschappen die u wilt aanpassen. - Elke
Parsemethode accepteert nu een optionele ParserConfiguration parameter die u kunt gebruiken om de parsering aan te passen. Wanneer deze niet is opgegeven, wordt de standaardconfiguratie gebruikt. - Om naamconflicten te voorkomen, is
Parserhernoemd naar CommandLineParser om verwarring met andere parsertypen te vermijden. Omdat het staatloos is, is het nu een statische klasse met alleen statische methoden. Er zijn tweeParseparsermethoden: één die eenIReadOnlyList<string> argsaccepteert en een andere die eenstring argsaccepteert. De laatste gebruikt CommandLineParser.SplitCommandLine(String) (ook openbaar) om de opdrachtregelinvoer in tokens te splitsen voordat deze wordt geparserd.
CommandLineBuilderExtensions is ook verwijderd. U kunt de methoden als volgt toewijzen aan de nieuwe API's:
CancelOnProcessTerminationis nu een eigenschap van InvocationConfiguration genaamd ProcessTerminationTimeout. Deze functie is standaard ingeschakeld, met een time-out van 2 seconden. Als u het wilt uitschakelen, stelt u deze in opnull. Zie Time-out voor procesbeëindiging voor meer informatie.EnableDirectives,UseEnvironmentVariableDirective,UseParseDirectiveenUseSuggestDirectivezijn verwijderd. Er is een nieuw Directive-type geïntroduceerd en RootCommand stelt nu een Directives eigenschap beschikbaar. U kunt instructies toevoegen, verwijderen en herhalen met behulp van deze verzameling. Suggestie-instructie is standaard opgenomen; u kunt ook andere instructies gebruiken, zoals DiagramDirective of EnvironmentVariablesDirective.EnableLegacyDoubleDashBehavioris verwijderd. Alle niet-overeenkomende tokens worden nu weergegeven door de ParseResult.UnmatchedTokens eigenschap. Zie Niet-gerelateerde tokens voor meer informatie.EnablePosixBundlingis verwijderd. De bundeling is nu standaard ingeschakeld. U kunt deze uitschakelen door de ParserConfiguration.EnablePosixBundling eigenschap in te stellen opfalse. Zie EnablePosixBundling voor meer informatie.RegisterWithDotnetSuggestis verwijderd omdat het een dure bewerking heeft uitgevoerd, meestal tijdens het opstarten van de toepassing. Nu moet u opdrachtendotnet suggesthandmatig registreren.UseExceptionHandleris verwijderd. De standaarduitzonderingshandler is nu standaard ingeschakeld; u kunt deze uitschakelen door de InvocationConfiguration.EnableDefaultExceptionHandler eigenschap in te stellen opfalse. Dit is handig als u uitzonderingen op een aangepaste manier wilt verwerken door deInvokeofInvokeAsyncmethoden in een try-catch-blok te verpakken. Zie EnableDefaultExceptionHandler voor meer informatie.UseHelpenUseVersionzijn verwijderd. De help en versie worden nu toegankelijk gemaakt door de HelpOption en VersionOption openbare typen. Ze zijn beide standaard opgenomen in de opties die zijn gedefinieerd door RootCommand. Zie Help-uitvoer aanpassen en versieoptie voor meer informatie.UseHelpBuilderis verwijderd. Voor meer informatie over hoe u de help-uitvoer kunt aanpassen, zie Hoe help aan te passen in System.CommandLine.AddMiddlewareis verwijderd. Het vertraagde het opstarten van de toepassing, en functies kunnen zonder deze worden uitgevoerd.UseParseErrorReportingenUseTypoCorrectionszijn verwijderd. De parseringsfouten worden nu standaard gerapporteerd bij het aanroepenParseResult. U kunt deze configureren met behulp van de ParseErrorAction actie die door de ParseResult.Action eigenschap wordt weergegeven.ParseResult result = rootCommand.Parse("myArgs", config); if (result.Action is ParseErrorAction parseError) { parseError.ShowTypoCorrections = true; parseError.ShowHelp = false; }UseLocalizationResourcesenLocalizationResourceszijn verwijderd. Deze functie is voornamelijk gebruikt door dedotnetCLI om ontbrekende vertalingen toe te voegen aanSystem.CommandLine. Al deze vertalingen zijn verplaatst naar het System.CommandLine zelf, dus deze functie is niet meer nodig. Als er ondersteuning voor uw taal ontbreekt, meldt u een probleem.UseTokenReplaceris verwijderd. Antwoordbestanden zijn standaard ingeschakeld, maar u kunt ze uitschakelen door de ResponseFileTokenReplacer eigenschap in te stellen opnull. U kunt ook een aangepaste implementatie bieden om aan te passen hoe antwoordbestanden worden verwerkt.
Ten slotte zijn de IConsole en alle gerelateerde interfaces (IStandardOut, IStandardError, IStandardIn) verwijderd.
InvocationConfiguration maakt twee TextWriter eigenschappen beschikbaar: Output en Error. U kunt deze eigenschappen instellen op elk TextWriter exemplaar, zoals een StringWriter, dat u kunt gebruiken om uitvoer vast te leggen voor testen. De motivatie voor deze wijziging was om minder typen weer te geven en bestaande abstracties opnieuw te gebruiken.
Aanroep
In 2.0.0-beta4 stelde de ICommandHandler interface de Invoke en InvokeAsync methoden beschikbaar, die werden gebruikt om de geparseerde opdracht aan te roepen. Hierdoor is het eenvoudig om synchrone en asynchrone code te combineren, bijvoorbeeld door een synchrone handler voor een opdracht te definiëren en deze vervolgens asynchroon aan te roepen (wat kan leiden tot een impasse). Bovendien was het mogelijk om alleen een handler te definiëren voor een opdracht, maar niet voor een optie (zoals help, die help weergeeft) of een instructie.
Er is een nieuwe abstracte basisklasse en twee afgeleide klassen geïntroduceerd, en CommandLineActioner is een nieuwe abstracte basisklasse SynchronousCommandLineAction en twee afgeleide klassen AsynchronousCommandLineAction geïntroduceerd. De vorige wordt gebruikt voor synchrone acties die een int afsluitcode retourneren, terwijl de laatste wordt gebruikt voor asynchrone acties die een Task<int> afsluitcode retourneren.
U hoeft geen afgeleid type te maken om een actie te definiëren. U kunt de Command.SetAction methode gebruiken om een actie voor een opdracht in te stellen. De synchrone actie kan een gemachtigde zijn die een System.CommandLine.ParseResult parameter accepteert en een int afsluitcode retourneert (of niets, en vervolgens wordt er een standaardafsluitcode 0 geretourneerd). De asynchrone actie kan een delegate zijn die System.CommandLine.ParseResult en CancellationToken parameters ontvangt en een Task<int> retourneert (of Task voor de standaard afsluitcode).
rootCommand.SetAction(ParseResult parseResult =>
{
FileInfo parsedFile = parseResult.GetValue(fileOption);
ReadFile(parsedFile);
});
In het verleden werd de CancellationToken doorgegeven aan InvokeAsync handler beschikbaar gesteld via een methode van InvocationContext:
rootCommand.SetHandler(async (InvocationContext context) =>
{
string? urlOptionValue = context.ParseResult.GetValueForOption(urlOption);
var token = context.GetCancellationToken();
returnCode = await DoRootCommand(urlOptionValue, token);
});
De meeste gebruikers hebben dit token niet verkregen en deze verder doorgegeven.
CancellationToken is nu een verplicht argument voor asynchrone acties, zodat de compiler een waarschuwing genereert wanneer deze niet verder wordt doorgegeven (zie CA2016).
rootCommand.SetAction((ParseResult parseResult, CancellationToken token) =>
{
string? urlOptionValue = parseResult.GetValue(urlOption);
return DoRootCommandAsync(urlOptionValue, token);
});
Als gevolg van deze en andere bovengenoemde wijzigingen is de InvocationContext klasse ook verwijderd. De ParseResult waarde wordt nu rechtstreeks doorgegeven aan de actie, zodat u rechtstreeks toegang hebt tot de geparseerde waarden en opties.
Ga als volgt te werk om deze wijzigingen samen te vatten:
- De
ICommandHandlerinterface is verwijderd.SynchronousCommandLineActionenAsynchronousCommandLineActionwerden geïntroduceerd. - De naam van de
Command.SetHandlermethode is gewijzigd in SetAction. - De naam van de
Command.Handlereigenschap is gewijzigd in Command.Action.Optionwerd uitgebreid met Option.Action. -
InvocationContextis verwijderd. DeParseResultwordt nu rechtstreeks aan de actie doorgegeven.
Zie Opdrachten parseren en aanroepen in System.CommandLinevoor meer informatie over het gebruik van acties.
De voordelen van de vereenvoudigde API
De wijzigingen die zijn aangebracht in 2.0.0-beta5 maken de API consistenter, toekomstbestendiger en eenvoudiger te gebruiken voor bestaande en nieuwe gebruikers.
Nieuwe gebruikers moeten minder concepten en typen leren, omdat het aantal openbare interfaces afneemt van 11 tot 0 en openbare klassen (en structs) is gedaald van 56 tot 38. Het aantal openbare methoden is gedaald van 378 tot 235 en openbare eigenschappen van 118 tot 99.
Het aantal assembly's waarnaar System.CommandLine wordt verwezen, wordt verminderd van 11 tot en met 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
De grootte van de bibliotheek wordt verkleind (met 32%) en dus ook de grootte van NativeAOT-apps die de bibliotheek gebruiken.
Eenvoud heeft ook de prestaties van de bibliotheek verbeterd (het is een neveneffect van het werk, niet het belangrijkste doel ervan). De benchmarks laten zien dat het parseren en aanroepen van opdrachten nu sneller is dan in 2.0.0-beta4, met name voor grote opdrachten met veel opties en argumenten. De prestatieverbeteringen zijn zichtbaar in zowel synchrone als asynchrone scenario's.
De eenvoudigste app, die eerder wordt gepresenteerd, produceert de volgende resultaten:
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 |
Zoals u kunt zien, is de opstarttijd (de benchmarks rapporteren de tijd die nodig is om het opgegeven uitvoerbare bestand uit te voeren) verbeterd met 12% vergeleken met 2.0.0-beta4. Als u de app compileert met NativeAOT, is deze slechts 3 ms langzamer dan een NativeAOT-app die de argumenten helemaal niet parseert (EmptyAOT in de bovenstaande tabel). Als u de overhead van een lege app (63,58 ms) uitsluit, is de parsering 40% sneller dan in 2.0.0-beta4 (22.22 ms vs 13.66 ms).