Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Important
System.CommandLine
est actuellement disponible en préversion et cette documentation concerne la version 2.0 bêta 5.
Certaines informations se rapportent à un produit en version préliminaire qui peut être sensiblement modifié avant sa sortie. Microsoft n’offre aucune garantie, expresse ou implicite, en ce qui concerne les informations fournies ici.
L’objectif principal de la version 2.0.0-beta5 était d’améliorer les API et de prendre un pas vers la publication d’une version stable de System.CommandLine. Les API ont été simplifiées et rendues plus cohérentes et cohérentes avec les directives de conception du framework. Cet article décrit les changements cassants qui ont été apportés dans la version 2.0.0-beta5 et le raisonnement derrière eux.
Changement de nom
Dans la version 2.0.0-beta4, tous les types et membres n’ont pas suivi les instructions d’affectation de noms. Certains n’étaient pas cohérents avec les conventions d’affectation de noms, telles que l’utilisation du préfixe pour les Is
propriétés booléennes. Dans la version 2.0.0-beta5, certains types et membres ont été renommés. Le tableau suivant présente les anciens noms et les nouveaux noms :
Ancien nom | Nouveau nom |
---|---|
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 |
Pour permettre à plusieurs erreurs pour le même symbole d’être signalé, la ErrorMessage
propriété a été convertie en méthode et renommée AddError
en .
Exposition de collections mutables
La version 2.0.0-beta4 a de nombreuses Add
méthodes utilisées pour ajouter des éléments à des collections, telles que des arguments, des options, des sous-commandes, des validateurs et des achèvements. Certaines de ces collections ont été exposées via des propriétés en tant que collections en lecture seule. En raison de cela, il était impossible de supprimer des éléments de ces collections.
Dans la version 2.0.0-beta5, nous avons modifié les API pour exposer des collections mutables au lieu de Add
méthodes et (parfois) de collections en lecture seule. Cela vous permet non seulement d’ajouter des éléments ou de les énumérer, mais également de les supprimer. Le tableau suivant présente l’ancienne méthode et les nouveaux noms de propriétés :
Nom de l’ancienne méthode | Nouvelle propriété |
---|---|
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 |
Les RemoveAlias
méthodes et HasAlias
les méthodes ont également été supprimées, car la Aliases
propriété est désormais une collection mutable. Vous pouvez utiliser la Remove
méthode pour supprimer un alias de la collection. Utilisez la Contains
méthode pour vérifier si un alias existe.
Noms et alias
Avant la version 2.0.0-beta5, il n’y avait aucune séparation claire entre le nom et les alias d’un symbole. Lorsqu’il name
n’a pas été fourni pour le Option<T>
constructeur, le symbole a signalé son nom comme l’alias le plus long avec des préfixes tels que --
, -
ou /
supprimé. C’était déroutant.
En outre, pour obtenir la valeur analysée, les utilisateurs ont dû stocker une référence à une option ou à un argument, puis l’utiliser pour obtenir la valeur à partir de ParseResult
.
Pour promouvoir la simplicité et l’explicite, le nom d’un symbole est désormais un paramètre obligatoire pour chaque constructeur de symboles (y compris Argument<T>
). Nous avons également séparé le concept d’un nom et d’alias ; désormais, les alias sont simplement des alias et n’incluent pas le nom du symbole. Bien sûr, ils sont facultatifs. Par conséquent, les modifications suivantes ont été apportées :
-
name
est maintenant un argument obligatoire pour chaque constructeur public deArgument<T>
,Option<T>
etCommand
. Dans le cas deArgument<T>
, il n’est pas utilisé pour l’analyse, mais pour générer l’aide. Dans le cas etCommand
, il est utilisé pour identifier le symbole pendant l’analyse et également pour obtenir deOption<T>
l’aide et des achèvements. - La
Symbol.Name
propriété n’est plusvirtual
; elle est désormais en lecture seule et renvoie le nom tel qu’il a été fourni lors de la création du symbole. En raison de cela,Symbol.DefaultName
a été supprimé etOption.Name
ne supprime plus le--
,-
ou/
tout autre préfixe de l’alias le plus long. - La
Aliases
propriété exposée parOption
etCommand
est maintenant une collection mutable. Cette collection n’inclut plus le nom du symbole. -
System.CommandLine.Parsing.IdentifierSymbol
a été supprimé (il s’agissait d’un type de base pour les deuxCommand
etOption
).
Le fait que le nom soit toujours présent permet d’obtenir la valeur analysée par nom :
RootCommand command = new("The description.")
{
new Option<int>("--number")
};
ParseResult parseResult = command.Parse(args);
int number = parseResult.GetValue<int>("--number");
Création d’options avec des alias
Dans le passé, Option<T>
il a exposé de nombreux constructeurs, dont certains ont accepté le nom. Étant donné que le nom est désormais obligatoire et que nous nous attendons à ce que les alias soient fréquemment fournis, Option<T>
il n’y a qu’un seul constructeur. Il accepte le nom et un params
tableau d’alias.
Avant 2.0.0-beta5, Option<T>
avait un constructeur qui prenait un nom et une description. En raison de cela, le deuxième argument peut maintenant être traité comme un alias plutôt qu’une description. Il s’agit du seul changement cassant connu dans l’API qui n’entraîne pas d’erreur du compilateur.
L’ancien code qui a utilisé le constructeur avec une description doit être mis à jour pour utiliser le nouveau constructeur qui prend un nom et des alias, puis définir la Description
propriété séparément. Par exemple:
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."
};
Valeurs par défaut et analyse personnalisée
Dans la version 2.0.0-beta4, les utilisateurs peuvent définir des valeurs par défaut pour les options et les arguments à l’aide des SetDefaultValue
méthodes. Ces méthodes ont accepté une object
valeur, qui n’était pas de type sécurisé et pouvait entraîner des erreurs d’exécution si la valeur n’était pas compatible avec l’option ou le type d’argument :
Option<int> option = new("--number");
option.SetDefaultValue("text"); // This is not type-safe, as the value is a string, not an int.
De plus, certains des Option
constructeurs ont Argument
accepté un délégué d’analyse et une valeur booléenne indiquant si le délégué était un analyseur personnalisé ou un fournisseur de valeurs par défaut. C’était déroutant.
Option<T>
et Argument<T>
les classes ont désormais une DefaultValueFactory
propriété qui peut être utilisée pour définir un délégué qui peut être appelé pour obtenir la valeur par défaut de l’option ou de l’argument. Ce délégué est appelé lorsque l’option ou l’argument est introuvable dans l’entrée de ligne de commande analysée.
Option<int> number = new("--number")
{
DefaultValueFactory = _ => 42
};
Argument<T>
et Option<T>
sont également fournis avec une CustomParser
propriété qui peut être utilisée pour définir un analyseur personnalisé pour le symbole :
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;
}
};
En outre, CustomParser
accepte un délégué de type Func<ParseResult, T>
, plutôt que le délégué précédent ParseArgument
. Cela et quelques autres délégués personnalisés ont été supprimés pour simplifier l’API et réduire le nombre de types exposés par l’API, ce qui réduit le temps de démarrage passé pendant la compilation JIT.
Pour plus d’exemples d’utilisation DefaultValueFactory
et CustomParser
de validation, consultez Comment personnaliser l’analyse et la validation dans System.CommandLine.
Séparation de l’analyse et de l’appel
Dans la version 2.0.0-beta4, il était possible de séparer l’analyse et l’appel de commandes, mais il était assez difficile de le faire.
Command
n’a pas exposé une Parse
méthode, mais CommandExtensions
fourni Parse
, Invoke
et InvokeAsync
les méthodes d’extension pour Command
. C’était déroutant, car il n’était pas clair quelle méthode utiliser et quand. Les modifications suivantes ont été apportées pour simplifier l’API :
-
Command
expose maintenant uneParse
méthode qui retourne unParseResult
objet. Cette méthode est utilisée pour analyser l’entrée de ligne de commande et retourner le résultat de l’opération d’analyse. En outre, il indique clairement que la commande n’est pas appelée, mais uniquement analysée et uniquement de manière synchrone. -
ParseResult
expose désormais les deuxInvoke
méthodesInvokeAsync
qui peuvent être utilisées pour appeler la commande. Cela permet d’indiquer clairement que la commande est appelée après l’analyse et autorise l’appel synchrone et asynchrone. - La
CommandExtensions
classe a été supprimée, car elle n’est plus nécessaire.
Paramétrage
Avant 2.0.0-beta5, il était possible de personnaliser l’analyse, mais seulement avec certaines méthodes publiques Parse
. Il y avait une Parser
classe qui expose deux constructeurs publics : l’un acceptant un Command
et l’autre acceptant un CommandLineConfiguration
.
CommandLineConfiguration
était immuable et pour le créer, vous deviez utiliser un modèle de générateur exposé par la CommandLineBuilder
classe. Les modifications suivantes ont été apportées pour simplifier l’API :
-
CommandLineConfiguration
a été mutable etCommandLineBuilder
a été supprimé. La création d’une configuration est désormais aussi simple que la création d’une instance et la définition desCommandLineConfiguration
propriétés que vous souhaitez personnaliser. En outre, la création d’une nouvelle instance de configuration est l’équivalent de la méthode d’appelCommandLineBuilder
UseDefaults
. - Chaque
Parse
méthode accepte désormais un paramètre facultatifCommandLineConfiguration
qui peut être utilisé pour personnaliser l’analyse. Lorsqu’elle n’est pas fournie, la configuration par défaut est utilisée. -
Parser
a été renommé pourCommandLineParser
lever l’ambiguïté des autres types d’analyseurs afin d’éviter les conflits de noms. Étant donné qu’il est sans état, il s’agit maintenant d’une classe statique avec uniquement des méthodes statiques. Il expose deuxParse
méthodes d’analyse : l’une acceptant uneIReadOnlyList<string> args
et une autre acceptant unstring args
. Ce dernier utiliseCommandLineParser.SplitCommandLine
(également public) pour fractionner l’entrée de ligne de commande en jetons avant de l’analyser.
CommandLineBuilderExtensions
a également été supprimé. Voici comment mapper ses méthodes aux nouvelles API :
CancelOnProcessTermination
est maintenant une propriété appeléeCommandLineConfiguration
ProcessTerminationTimeout. Elle est activée par défaut, avec un délai d’expiration de 2s. Définissez-le pournull
le désactiver.EnableDirectives
,UseEnvironmentVariableDirective
,UseParseDirective
etUseSuggestDirective
ont été supprimés. Un nouveau type de directive a été introduit et rootCommand expose désormais laSystem.CommandLine.RootCommand.Directives
propriété. Vous pouvez ajouter, supprimer et itérer des directives à l’aide de cette collection. La directive suggest est incluse par défaut ; vous pouvez également utiliser d’autres directives telles que DiagramDirective ouEnvironmentVariablesDirective
.EnableLegacyDoubleDashBehavior
a été supprimé. Tous les jetons sans correspondance sont désormais exposés par la propriété ParseResult.UnmatchedTokens .EnablePosixBundling
a été supprimé. Le regroupement est désormais activé par défaut, vous pouvez le désactiver en définissant la propriétéfalse
CommandLineConfiguration.EnableBundling sur .RegisterWithDotnetSuggest
a été supprimé lors de l’exécution d’une opération coûteuse, généralement pendant le démarrage de l’application. Vous devez maintenant inscrire des commandes manuellementdotnet suggest
.UseExceptionHandler
a été supprimé. Le gestionnaire d’exceptions par défaut est désormais activé par défaut, vous pouvez le désactiver en définissant la propriétéfalse
CommandLineConfiguration.EnableDefaultExceptionHandler sur . Cela est utile lorsque vous souhaitez gérer les exceptions d’une manière personnalisée, en encapsulant simplement les méthodes ouInvokeAsync
lesInvoke
méthodes dans un bloc try-catch.UseHelp
etUseVersion
ont été supprimés. L’aide et la version sont désormais exposées par les types publics HelpOption et VersionOption . Ils sont tous deux inclus par défaut dans les options définies par RootCommand.UseHelpBuilder
a été supprimé. Pour plus d’informations sur la personnalisation de la sortie d’aide, consultez Comment personnaliser l’aide dans System.CommandLine.AddMiddleware
a été supprimé. Il ralentit le démarrage de l’application et les fonctionnalités peuvent être exprimées sans elle.UseParseErrorReporting
etUseTypoCorrections
ont été supprimés. Les erreurs d’analyse sont désormais signalées par défaut lors de l’appelParseResult
. Vous pouvez le configurer à l’aide de laParseErrorAction
propriété exposéeParseResult.Action
.ParseResult result = rootCommand.Parse("myArgs", config); if (result.Action is ParseErrorAction parseError) { parseError.ShowTypoCorrections = true; parseError.ShowHelp = false; }
UseLocalizationResources
etLocalizationResources
ont été supprimés. Cette fonctionnalité a été utilisée principalement par l’interfacedotnet
CLI pour ajouter des traductions manquantes àSystem.CommandLine
. Toutes ces traductions ont été déplacées vers le System.CommandLine lui-même, de sorte que cette fonctionnalité n’est plus nécessaire. Si nous ne prenons pas en charge votre langue, signalez un problème.UseTokenReplacer
a été supprimé. Les fichiers de réponse sont activés par défaut, mais vous pouvez les désactiver en définissant laSystem.CommandLine.CommandLineConfiguration.ResponseFileTokenReplacer
propriéténull
sur . Vous pouvez également fournir une implémentation personnalisée pour personnaliser le traitement des fichiers de réponse.
Enfin, mais pas le moins, les IConsole
interfaces associées (IStandardOut
, IStandardError
, IStandardIn
) ont été supprimées.
System.CommandLine.CommandLineConfiguration
expose deux TextWriter
propriétés : Output
et Error
. Celles-ci peuvent être définies sur n’importe quelle TextWriter
instance, telle qu’un StringWriter
, qui peut être utilisée pour capturer la sortie pour les tests. Notre motivation était d’exposer moins de types et de réutiliser les abstractions existantes.
Appel
Dans la version 2.0.0-beta4, l’interface ICommandHandler
a exposé Invoke
et InvokeAsync
les méthodes utilisées pour appeler la commande analysée. Cela facilite la combinaison de code synchrone et asynchrone, par exemple en définissant un gestionnaire synchrone pour une commande, puis en l’appelant de manière asynchrone (ce qui peut entraîner un blocage). De plus, il était possible de définir un gestionnaire uniquement pour une commande, mais pas pour une option (comme l’aide, qui affiche de l’aide) ou une directive.
Une nouvelle classe System.CommandLine.CommandLineAction
de base abstraite et deux classes dérivées : System.CommandLine.SynchronousCommandLineAction
et System.CommandLine.AsynchronousCommandLineAction
ont été introduites. L’ancien est utilisé pour les actions synchrones qui retournent un code de int
sortie, tandis que celui-ci est utilisé pour les actions asynchrones qui retournent un Task<int>
code de sortie.
Vous n’avez pas besoin de créer un type dérivé pour définir une action. Vous pouvez utiliser la System.CommandLine.Command.SetAction
méthode pour définir une action pour une commande. L’action synchrone peut être un délégué qui prend un System.CommandLine.ParseResult
paramètre et retourne un code de int
sortie (ou rien, puis un code de sortie par défaut 0
est retourné). L’action asynchrone peut être un délégué qui prend un System.CommandLine.ParseResult
et CancellationToken des paramètres et retourne un Task<int>
(ou Task
pour obtenir le code de sortie par défaut retourné).
rootCommand.SetAction(ParseResult parseResult =>
{
FileInfo parsedFile = parseResult.GetValue(fileOption);
ReadFile(parsedFile);
});
Dans le passé, le CancellationToken
passage à InvokeAsync
a été exposé au gestionnaire via une méthode de InvocationContext
:
rootCommand.SetHandler(async (InvocationContext context) =>
{
string? urlOptionValue = context.ParseResult.GetValueForOption(urlOption);
var token = context.GetCancellationToken();
returnCode = await DoRootCommand(urlOptionValue, token);
});
La majorité de nos utilisateurs n’obtenaient pas ce jeton et le transmettaient plus loin. Nous avons fait CancellationToken
un argument obligatoire pour les actions asynchrones, afin que le compilateur produise un avertissement lorsqu’il n’est pas passé plus loin (CA2016).
rootCommand.SetAction((ParseResult parseResult, CancellationToken token) =>
{
string? urlOptionValue = parseResult.GetValue(urlOption);
return DoRootCommandAsync(urlOptionValue, token);
});
Suite à ces modifications et à d’autres modifications forementées, la InvocationContext
classe a également été supprimée. La ParseResult
valeur est désormais transmise directement à l’action. Vous pouvez donc accéder aux valeurs et options analysées directement à partir de celle-ci.
Pour résumer ces modifications :
- L’interface
ICommandHandler
a été supprimée.SynchronousCommandLineAction
etAsynchronousCommandLineAction
ont été introduits. - La
Command.SetHandler
méthode a été renomméeSetAction
en . - La
Command.Handler
propriété a été renomméeCommand.Action
en .Option
a été étendu avecOption.Action
. -
InvocationContext
a été supprimé. LaParseResult
valeur est désormais transmise directement à l’action.
Pour plus d’informations sur l’utilisation d’actions, consultez Comment analyser et appeler des commandes dans System.CommandLine.
Avantages de l’API simplifiée
Nous espérons que les modifications apportées dans la version 2.0.0-beta5 feront en sorte que l’API soit plus cohérente, plus future et plus facile à utiliser pour les utilisateurs existants et nouveaux.
De nouveaux utilisateurs doivent apprendre moins de concepts et de types, car le nombre d’interfaces publiques a diminué de 11 à 0, et les classes publiques (et les structs) ont diminué de 56 à 38. Le nombre de méthodes publiques est passé de 378 à 235 et les propriétés publiques de 118 à 99.
Le nombre d’assemblys référencés par System.CommandLine est réduit de 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
Il nous a permis de réduire la taille de la bibliothèque de 32% et la taille de l’application NativeAOT suivante de 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}");
}
La simplicité a également amélioré les performances de la bibliothèque (c’est un effet secondaire du travail, et non l’objectif principal de celui-ci). Les benchmarks montrent que l’analyse et l’appel de commandes sont désormais plus rapides que dans la version 2.0.0-beta4, en particulier pour les commandes volumineuses avec de nombreuses options et arguments. Les améliorations des performances sont visibles dans les scénarios synchrones et asynchrones.
Pour l’application la plus simple présentée précédemment, nous avons obtenu les résultats suivants :
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 |
Comme vous pouvez le voir, l’heure de démarrage (les benchmarks signalent le temps nécessaire à l’exécution d’un exécutable donné) s’est améliorée de 12% par rapport à 2.0.0-beta4. Si nous compilez l’application avec NativeAOT, il ne s’agit que de 3 ms plus lentement qu’une application NativeAOT qui n’analyse pas du tout les arguments (EmptyAOT dans le tableau ci-dessus). En outre, lorsque nous excluons la surcharge d’une application vide (63,58 ms), l’analyse est de 40% plus rapide que dans 2.0.0-beta4 (22,22 ms et 13,66 ms).
Voir aussi
- vue d’ensemble System.CommandLine