März 2019

Band 34, Nummer 3

[.NET]

Analysieren der Befehlszeile mit System.CommandLine

Von Mark Michaelis | März 2019

Im Rückblick auf die verschiedenen Versionen bis hin zu .NET Framework 1.0 musste ich erstaunt feststellen, dass es für Entwickler noch nie einen einfachen Weg gab, die Befehlszeile ihrer Anwendungen zu analysieren. Anwendungen starten die Ausführung von der Main-Methode aus, aber die Argumente werden als Array (string[] args) übergeben, ohne Unterscheidung, welche Elemente im Array Befehle, Optionen, Argumente und dergleichen sind.

Ich habe mich in einem früheren Artikel („How to Contribute to Microsoft Open Source Software Projects“ (Mitwirken an Open-Source-Softwareprojekten von Microsoft), msdn.com/magazine/mt830359) mit diesem Problem befasst und meine Zusammenarbeit mit Jon Sequeira von Microsoft beschrieben. Sequeira hat ein Open-Source-Team von Entwicklern dazu veranlasst, einen neuen Befehlszeilenparser zu erstellen, der Befehlszeilenargumente akzeptiert und diese in eine API namens System.CommandLine analysiert, die drei Aufgaben übernimmt:

  • Sie ermöglicht die Konfiguration einer Befehlszeile.
  • Sie ermöglicht das Analysieren von generischen Befehlszeilenargumenten (Token) in unterschiedliche Konstrukte, wobei jedes Wort in der Befehlszeile ein Token ist. (Technisch gesehen ermöglichen Befehlszeilenhosts das Kombinieren von Wörtern zu einem einzelnen Token mithilfe von Anführungszeichen.)
  • Sie ruft Funktionen basierend auf dem Befehlszeilenwert auf, die für die Ausführung konfiguriert sind.

Zu den unterstützten Konstrukten gehören Befehle, Optionen, Argumente, Anweisungen, Trennzeichen und Aliase. Hier ist eine Beschreibung der einzelnen Konstrukte:

Befehle: Dies sind die Aktionen, die von der Anwendungsbefehlszeile unterstützt werden. Betrachten wir zum Beispiel Git. Einige der integrierten Befehle für Git sind „branch“, „add“, „status“, „commit“ und „push“. Technisch gesehen sind die nach dem Namen der ausführbaren Datei angegebenen Befehle tatsächlich Unterbefehle. Unterbefehle des Stammbefehls – der Name der ausführbaren Datei selbst (z.B. „git.exe“) – können selbst eigene Unterbefehle aufweisen. Beispielsweise weist der Befehl „dotnet add package“ „dotnet“ als Stammbefehl, „add“ als Unterbefehl und „package“ als Unterbefehl zu „add“ auf (vielleicht nennen wir dies den Unter-Unterbefehl?).

Optionen: Diese bieten eine Möglichkeit, das Verhalten eines Befehls zu ändern. Der Befehl „dotnet build“ enthält beispielsweise die Option „--no-restore“, die Sie angeben können, um zu verhindern, dass der restore-Befehl implizit ausgeführt wird (stattdessen wird auf die vorherige Ausführung des restore-Befehls zurückgegriffen). Wie der Name schon sagt, sind Optionen in der Regel kein erforderliches Element eines Befehls.

Argumente: Sowohl Befehle als auch Optionen können zugehörige Werte aufweisen. Der Befehl „dotnet new“ enthält beispielsweise den Namen der Vorlage. Dieser Wert ist erforderlich, wenn Sie den neuen Befehl angeben. In ähnlicher Weise können auch Optionen Werte zugeordnet sein. Auch bei „dotnet new“ weist die Option „--name“ ein Argument für die Angabe des Projektnamens auf. Der Wert, der einem Befehl oder einer Option zugeordnet ist, wird als „Argument“ bezeichnet.

Anweisungen: Dabei handelt es sich um Befehle, die für alle Anwendungen bereichsübergreifend sind. Ein Umleitungsbefehl kann beispielsweise bewirken, dass alle Ausgaben (stderr und stdout) in einem XML-Format ausgegeben werden. Da Anweisungen Teil des System.CommandLine-Frameworks sind, werden sie automatisch eingebunden, ohne dass der Befehlszeilen-Schnittstellenentwickler irgendwelchen Aufwand betreiben muss.

Trennzeichen: Die Zuordnung eines Arguments zu einem Befehl oder einer Option erfolgt über ein Trennzeichen. Häufige Trennzeichen sind das Leerzeichen, der Doppelpunkt und das Gleichheitszeichen. Wenn Sie beispielsweise die Ausführlichkeit eines dotnet-Builds angeben, können Sie jede der folgenden drei Varianten verwenden: --verbosity=diagnostic, --verbosity diagnostic oder --verbosity:diagnostic.

Aliase: Hierbei handelt es sich um zusätzliche Namen, die zur Identifizierung von Befehlen und Optionen verwendet werden können. Bei dotnet ist beispielsweise „classlib“ ein Alias für „Class library“ und „-v“ ein Alias für „--verbosity“.

Vor System.CommandLine bedeutete das Fehlen einer integrierten Parserunterstützung, dass Sie als Entwickler beim Start Ihrer Anwendung das Array der Argumente analysieren mussten, um festzustellen, welches Argument welchem Argumenttyp entsprach. Dann mussten Sie alle Werte richtig zuordnen. Zwar enthält .NET zahlreiche Versuche, dieses Problem zu lösen, aber keiner hat sich als Standardlösung herausgestellt, und keiner lässt sich gut skalieren, um sowohl einfache als auch komplexe Szenarien zu unterstützen. Vor diesem Hintergrund wurde System.CommandLine in Alphaform entwickelt und freigegeben (siehe github.com/dotnet/command-line-api).

Einfache Dinge einfach halten

Stellen Sie sich vor, Sie schreiben ein Bildkonvertierungsprogramm, das eine Bilddatei basierend auf dem angegebenen Ausgabennamen in ein anderes Format konvertiert. Die Befehlszeile könnte etwa wie folgt aussehen:

imageconv --input sunrise.CR2 --output sunrise.JPG

Mit dieser Befehlszeile (unter „Übergeben von Parametern an die ausführbare .NET Core-Datei“ finden Sie eine alternative Befehlszeilensyntax) startet das Programm imageconv im Main-Einstiegspunkt, static void Main(string[] args), mit einem Zeichenfolgenarray aus vier entsprechenden Argumenten. Leider gibt es keine Zuordnung zwischen „--input“ und „sunrise.CR2“ oder zwischen „--output“ und „sunrise.JPG“. Es gibt auch keinen Hinweis darauf, dass „--input“ und „--output“ Optionen identifizieren.

Glücklicherweise bietet die neue System.CommandLine-API eine deutliche Verbesserung gegenüber diesem einfachen Szenario, und zwar auf eine Art und Weise, die ich bisher noch nicht gesehen habe. Die Vereinfachung besteht darin, dass Sie einen Main-Einstiegspunkt mit einer Signatur programmieren können, die mit der Befehlszeile übereinstimmt. Das heißt, dass die Signatur für Main folgendermaßen aussieht:

static void Main(string input, string output)

Sie sehen richtig: System.CommandLine ermöglicht die automatische Konvertierung der --input- und --output-Optionen in Parameter für Main und eliminiert die Notwendigkeit, einen Standardeinstiegspunkt Main(string[] args) zu schreiben. Die einzige zusätzliche Anforderung besteht darin, auf eine Assembly zu verweisen, die dieses Szenario ermöglicht. Details zu diesen Verweisen finden Sie unter itl.tc/syscmddf, da alle hier bereitgestellten Anweisungen nach der Freigabe der Assembly auf NuGet wahrscheinlich schnell veraltet sein werden. (Nein, es gibt keine Sprachänderung, um dies zu unterstützen. Vielmehr wird beim Hinzufügen des Verweises die Projektdatei dahingehend geändert, dass sie einen Buildtask enthält, der eine Main-Standardmethode mit einem Textkörper generiert, der Reflektion verwendet, um den „benutzerdefinierten“ Einstiegspunkt aufzurufen.)

Darüber hinaus sind Argumente nicht auf Zeichenfolgen beschränkt. Es gibt eine Vielzahl von integrierten Konvertern (und Unterstützung für benutzerdefinierte Konverter), mit denen Sie z.B. System.IO.FileInfo für den Parametertyp für die Ein- und Ausgabe verwenden können:

static void Main(FileInfo input, FileInfo output)

Wie im Artikelabschnitt „System.CommandLine-Architektur“ beschrieben, wird System.CommandLine in ein Kernmodul und ein App-Anbietermodul unterteilt. Die Konfiguration der Befehlszeile über Main ist eine App-Modellimplementierung, aber im Moment beziehe ich mich nur auf die gesamte API-Sammlung als System.CommandLine.

Die Zuordnung zwischen Befehlszeilenargumenten und Main-Methodenparametern ist heute allgemein, aber für viele Programme noch relativ gut geeignet. Sehen wir uns eine etwas komplexere imageconv-Befehlszeile an, die einige der zusätzlichen Funktionen aufzeigt. Abbildung 1 zeigt die Befehlszeilenhilfe.

Abbildung 1: Beispielbefehlszeile für imageconv

imageconv:
  Converts an image file from one format to another.
Usage:
  imageconv [options]
Options:
  --input          The path to the image file that is to be converted.
  --output         The target name of the output after conversion.
  --x-crop-size    The X dimension size to crop the picture.
                   The default is 0 indicating no cropping is required.
  --y-crop-size    The Y dimension size to crop the picture.
                   The default is 0 indicating no cropping is required.
  --version        Display version information

Die entsprechende Main-Methode, die diese aktualisierte Befehlszeile aktiviert, wird in Abbildung 2 gezeigt. Auch wenn das Beispiel nichts anderes als eine vollständig dokumentierte Main-Methode aufweist, gibt es zahlreiche Funktionen, die automatisch aktiviert werden. Untersuchen wir die Funktionalität, die in die Verwendung von System.CommandLine integriert ist.

Abbildung 2: Main-Methode mit Unterstützung der aktualisierten imageconv-Befehlszeile

/// <summary>/// Converts an image file from one format to another./// </summary>/// <param name="input">The path to the image file that is to be
    converted.</param>/// <param name="output">The name of the output from the conversion.
    </param>/// <param name="xCropSize">The x dimension size to crop the picture.
    The default is 0 indicating no cropping is required.</param>/// <param name="yCropSize">The x dimension size to crop the picture.
    The default is 0 indicating no cropping is required.</param>
public static void Main(  FileInfo input, FileInfo output,   int xCropSize = 0, int yCropSize = 0)

Eine erste Funktionalität ist die Hilfeausgabe für die Befehlszeile, die aus den XML-Kommentaren zu Main abgeleitet wird. Diese Kommentare ermöglichen nicht nur eine allgemeine Beschreibung des Programms (angegeben im XML-Zusammenfassungskommentar), sondern auch die Dokumentation zu jedem Argument mithilfe von XML-Parameterkommentaren. Die Nutzung der XML-Kommentare erfordert die Aktivierung der Dokumentausgabe, die jedoch automatisch für Sie konfiguriert wird, wenn Sie auf die Assembly verweisen, die die Konfiguration über Main ermöglicht. Sie können die integrierte Hilfeausgabe mit drei verschiedenen Befehlszeilenoptionen abrufen: -h, -? und --help. So wird beispielsweise die in Abbildung 1 angezeigte Hilfe automatisch von System.CommandLine generiert.

Auch wenn es keinen Versionsparameter für Main gibt, generiert System.CommandLine auf ähnliche Weise automatisch eine Option „--version“, die die Assemblyversion der ausführbaren Datei ausgibt.

Eine weitere Funktion (die Befehlszeilen-Syntaxüberprüfung) erkennt, ob ein erforderliches Argument (für das kein Standardwert für den Parameter angegeben ist) fehlt. Wenn ein erforderliches Argument nicht angegeben wird, gibt System.CommandLine automatisch einen Fehler aus, der folgendermaßen lautet: „Required argument missing for option: --output“ (Erforderliches Argument fehlt für Option: --output). Obwohl dies nicht gerade intuitiv ist, sind standardmäßig Optionen mit Argumenten erforderlich. Wenn jedoch der Argumentwert, der einer Option zugeordnet ist, nicht erforderlich ist, können Sie die C#-Standardsyntax für Parameterwerte nutzen. Beispiele:

int xCropSize = 0

Es gibt auch eine integrierte Unterstützung zum Analysieren von Optionen unabhängig von der Reihenfolge, in der die Optionen in der Befehlszeile aufgeführt werden. Und es ist erwähnenswert, dass das Trennzeichen zwischen der Option und dem Argument standardmäßig ein Leerzeichen, ein Doppelpunkt oder ein Gleichheitszeichen sein kann. Schließlich wird die Camel-Case-Schreibweise für die Parameternamen von Main in Argumentnamen im Posix-Stil konvertiert (d.h. „xCropSize“ wird in der Befehlszeile in „--x-crop-size“ übersetzt).

Wenn Sie eine nicht erkannte Option oder einen Befehlsnamen eingeben, gibt System.CommandLine automatisch einen Befehlszeilenfehler zurück, der folgendermaßen lautet: „Unrecognized command or argument“ (Nicht erkannter Befehl oder nicht erkanntes Argument). Wenn der angegebene Name jedoch einer vorhandenen Option ähnelt, wird der Fehler mit einem Vorschlag zur Tippfehlerkorrektur angezeigt.

Es gibt einige integrierte Anweisungen, die für alle Befehlszeilenanwendungen verfügbar sind, die System.CommandLine verwenden. Diese Anweisungen sind in eckige Klammern eingeschlossen und werden unmittelbar nach dem Namen der Anwendung angezeigt. Beispielsweise löst die Anweisung [debug] einen Breakpoint aus, der es Ihnen ermöglicht, einen Debugger anzufügen, während [parse] eine Vorschau anzeigt, wie Token analysiert werden:

imageconv [parse] --input sunrise.CR2 --output sunrise.JPG

Darüber hinaus werden automatisierte Tests über eine IConsole-Schnittstelle und die Implementierung der TestConsole-Klasse unterstützt. Um die TestConsole in die Befehlszeilenpipeline einzubinden, fügen Sie Main einen IConsole-Parameter hinzu:

public static void Main(
  FileInfo input, FileInfo output,
  int xCropSize = 0, int yCropSize = 0,
    IConsole console = null)

Um den Konsolenparameter zu nutzen, ersetzen Sie Aufrufe von System.Console durch den Parameter IConsole. Beachten Sie, dass der IConsole-Parameter automatisch für Sie festgelegt wird, wenn er direkt aus der Befehlszeile (und nicht aus einem Komponententest) aufgerufen wird, sodass der Parameter, obwohl ihm standardmäßig NULL zugewiesen ist, keinen NULL-Wert haben sollte – es sei denn, Sie schreiben Testcode, der ihn auf diese Weise aufruft. Alternativ können Sie auch den IConsole-Parameter an erster Stelle nennen.

Eine meiner Lieblingsfunktionen ist die Unterstützung für Vervollständigung mit der TAB-TASTE, die Endbenutzer optional durch Ausführen eines Befehls zur Aktivierung aktivieren können (siehe bit.ly/2sSRsQq). Dies ist ein optionales Szenario, da Benutzer in der Regel bei impliziten Änderungen an der Shell vorsichtig sind. Die Vervollständigung mit der TAB-TASTE für Optionen und Befehlsnamen erfolgt automatisch. Es gibt jedoch auch Vervollständigung mit der TAB-TASTE für Argumente über Vorschläge. Bei der Konfiguration eines Befehls oder einer Option können die Werte für die Vervollständigung mit der TAB-TASTE aus einer statischen Liste von Werten stammen, z.B. q, m, n, n, d oder Diagnosewerte von „--verbosity“. Oder sie können dynamisch zur Laufzeit bereitgestellt werden, z.B. aus einem REST-Aufruf, der eine Liste der verfügbaren NuGet-Pakete zurückgibt, wenn das Argument ein NuGet-Verweis ist.

Die Verwendung der Main-Methode als Spezifikation für die Befehlszeile ist nur eine von mehreren Möglichkeiten, wie Sie mit System.CommandLine programmieren können. Die Architektur ist flexibel und bietet auch andere Optionen, die Befehlszeile zu definieren und mit ihr zu arbeiten.

Die System.CommandLine-Architektur

System.CommandLine ist um eine Kernassembly herum aufgebaut, die eine API zur Konfiguration der Befehlszeile und einen Parser enthält, der die Befehlszeilenargumente in eine Datenstruktur auflöst. Alle im vorherigen Abschnitt aufgeführten Funktionen können über die Kernassembly aktiviert werden (mit Ausnahme der Aktivierung einer anderen Methodensignatur für Main). Die Unterstützung für die Konfiguration der Befehlszeile, insbesondere unter Verwendung einer domänenspezifischen Sprache (z.B. in der Art einer Main-Methode), wird jedoch durch ein App-Modell ermöglicht. (Das App-Modell, das für die zuvor beschriebene Implementierung in der Art einer Main-Methode verwendet wird, trägt den Codenamen „DragonFruit“.) Die System.CommandLine-Architektur ermöglicht jedoch die Unterstützung zusätzlicher App-Modelle (wie in Abbildung 3 gezeigt).

System.CommandLine-Architektur
Abbildung 3: System.CommandLine-Architektur

Sie können beispielsweise ein App-Modell schreiben, das ein C#-Klassenmodell verwendet, um die Befehlszeilensyntax für eine Anwendung zu definieren. In einem solchen Modell können Eigenschaftennamen dem Optionsnamen und der Eigenschaftentyp dem Datentyp entsprechen, in den ein Argument konvertiert werden soll. Darüber hinaus kann das Modell Attribute nutzen, um z.B. Aliase zu definieren. Alternativ können Sie auch ein Modell schreiben, das eine docopt-Datei (siehe docopt.org) für die Konfiguration analysiert. Jedes dieser App-Modelle würde die System.CommandLine-Konfigurations-API aufrufen. Natürlich können Entwickler es vorziehen, System.CommandLine direkt aus ihrer Anwendung heraus aufzurufen, anstatt über ein App-Modell, und dieser Ansatz wird ebenfalls unterstützt.

Übergeben von Parametern an die ausführbare .NET Core-Datei

Bei der Angabe von Befehlszeilenargumenten in Kombination mit dem Befehl „dotnet run“ würde die vollständige Befehlszeile wie folgt aussehen:

dotnet run --project imageconv.csproj -- --input sunrise.CR2
  --output sunrise.JPG

Wenn Sie „dotnet“ aus dem gleichen Verzeichnis ausführen, in dem sich die CSPROJ-Datei befunden hat, würde die Befehlszeile jedoch so lauten:

dotnet run -- --input sunrise.CR2 --output sunrise.JPG

Der Befehl „dotnet run“ verwendet „--“ als Bezeichner, der angibt, dass alle anderen Argumente an die ausführbare Datei zur Analyse übergeben werden sollen.

Ab .NET Core 2.2 ist auch Unterstützung für eigenständige Anwendungen verfügbar (sogar unter Linux). Eine eigenständige Anwendung können Sie ohne Verwendung von „dotnet run“ starten und sich stattdessen einfach auf die sich ergebende ausführbare Datei verlassen:

imageconv.exe --input sunrise.CR2 --output sunrise.JPG

Offensichtlich ist dies das erwartete Verhalten für Benutzer von Windows.

Das Komplexe möglich machen

Bereits zuvor habe ich erwähnt, dass die Funktionalität, einfache Dinge einfach zu halten, sehr elementar war. Dies liegt daran, dass für die Aktivierung der Befehlszeilenanalyse über die Main-Methode noch einige Funktionen fehlen, die einige Entwickler für wichtig halten könnten. Beispielsweise können Sie keinen (Unter-)Befehl oder Optionsalias konfigurieren. Wenn Sie auf diese Einschränkungen stoßen, können Sie Ihr eigenes App-Modell erstellen oder einen Aufruf direkt in den Core (System.CommandLine-Assembly) vornehmen.

System.CommandLine enthält Klassen, die die Konstrukte einer Befehlszeile darstellen. Dies schließt „Command“ (und RootCommand), „Option“ und „Argument“ ein. Abbildung 4 zeigt Beispielcode für den direkten Aufruf von System.CommandLine und die Konfiguration, um die im Hilfetext von Abbildung 1 definierte Grundfunktionalität zu erreichen.

Abbildung 4: Direktes Arbeiten mit System.CommandLine

using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.IO;
...
public static async Task<int> Main(params string[] args)
{
  RootCommand rootCommand = new RootCommand(
    description: "Converts an image file from one format to another."
    , treatUnmatchedTokensAsErrors: true);
  Option inputOption = new Option(
    aliases: new string[] { "--input", "-i" }
    , description: "The path to the image file that is to be converted."
    , argument: new Argument<FileInfo>());
  rootCommand.AddOption(inputOption);
  Option outputOption = new Option(
    aliases: new string[] { "--output", "-o" }
    , description: "The target name of the output file after conversion."
    , argument: new Argument<FileInfo>());
  rootCommand.AddOption(outputOption);
  Option xCropSizeOption = new Option(
    aliases: new string[] { "--x-crop-size", "-x" }
    , description: "The x dimension size to crop the picture. 
      The default is 0 indicating no cropping is required."
    , argument: new Argument<FileInfo>());
  rootCommand.AddOption(xCropSizeOption);
  Option yCropSizeOption = new Option(
    aliases: new string[] { "--y-crop-size", "-y" }
    , description: "The Y dimension size to crop the picture. 
      The default is 0 indicating no cropping is required."
    , argument: new Argument<FileInfo>());
  rootCommand.AddOption(yCropSizeOption);
  rootCommand.Handler =
    CommandHandler.Create<FileInfo, FileInfo, int, int>(Convert);
  return await rootCommand.InvokeAsync(args);
}
static public void Convert(
  FileInfo input, FileInfo output, int xCropSize = 0, int yCropSize = 0)
{
  // Convert...
}

In diesem Beispiel wird jedes Konstrukt explizit instanziiert, anstatt sich auf ein Main-App-Model zu verlassen, um die Befehlszeilenkonfiguration zu definieren. Der einzige funktionale Unterschied besteht im Hinzufügen von Aliasen für jede Option. Die direkte Nutzung der Kern-API bietet jedoch mehr Kontrolle als das, was mit dem Ansatz in der Art einer Main-Methode möglich ist.

Sie können beispielsweise Unterbefehle definieren, z.B. einen Befehl „image-enhance“, der einen eigenen Satz von Optionen und Argumenten für die Optimierungsaktion enthält. Komplexe Befehlszeilenprogramme verfügen über mehrere Unterbefehle und sogar Unter-Unterbefehle. Der dotnet-Befehl verfügt beispielsweise über den Befehl „dotnet sln add“, wobei „dotnet“ der Stammbefehl, „sln“ einer der vielen Unterbefehle und „add“ (oder „list“ und „remove“) ein untergeordneter Befehl von „sln“ ist.

Der abschließende Aufruf von InvokeAsync richtet implizit viele der Funktionen automatisch ein, darunter:

  • Anweisungen für die Analyse und das Debuggen.
  • Die Konfiguration der Hilfe- und Versionsoptionen.
  • Vervollständigung mit der TAB-TASTE und Korrektur von Tippfehlern.

Es sind auch separate Erweiterungsmethoden für jede Funktion verfügbar, wenn eine differenziertere Steuerung erforderlich ist. Es gibt auch zahlreiche andere Konfigurationsmöglichkeiten, die durch die Core-API bereitgestellt werden. Hierzu gehören:

  • Behandlung von Token, die von der Konfiguration explizit ohne Entsprechung festgelegt sind.
  • Vorschlagshandler, die die Vervollständigung mit der TAB-TASTE ermöglichen und eine Liste möglicher Werte unter Berücksichtigung der aktuellen Befehlszeilen-Zeichenfolge und der Position des Cursors zurückgeben.
  • Ausgeblendete Befehle, die nicht durch die Vervollständigung mit der TAB-TASTE oder die Hilfe erkannt werden sollen.

Darüber hinaus gibt es zwar viele Regler und Schaltflächen, um die Befehlszeilenanalyse mit System.CommandLine zu steuern, aber es wird auch ein Method-First-Ansatz bereitgestellt. In der Tat wird dieser Ansatz intern verwendet, um eine Bindung an die Main-ähnliche Methode herzustellen. Mit dem Method-First-Ansatz können Sie eine Methode wie Convert (unten in Abbildung 4) verwenden, um den Parser zu konfigurieren (wie in Abbildung 5 gezeigt).

Abbildung 5: Verwenden des Method-First-Ansatzes zum Konfigurieren von System.CommandLine

public static async Task<int> Main(params string[] args)
{
  RootCommand rootCommand = new RootCommand(
    description: "Converts an image file from one format to another."
    , treatUnmatchedTokensAsErrors: true);
  MethodInfo method = typeof(Program).GetMethod(nameof(Convert));
  rootCommand.ConfigureFromMethod(method);
  rootCommand.Children["--input"].AddAlias("-i");
  rootCommand.Children["--output"].AddAlias("-o");
  return await rootCommand.InvokeAsync(args);
}

Beachten Sie in diesem Fall, dass für die Erstkonfiguration die Convert-Methode verwendet wird. Anschließend navigieren Sie durch das Objektmodell des Stammbefehls, um Aliase hinzuzufügen. Die indizierbare Eigenschaft „Children“ enthält alle Optionen und Befehle, die an den Stammbefehl angefügt sind.

Zusammenfassung

Ich freue mich sehr über die Funktionalität von System.CommandLine. Die Tatsache, dass das Erreichen der hier untersuchten einfachen Szenarien so wenig Code erfordert, ist wunderbar. Darüber hinaus bedeutet der Funktionsumfang, der bereitgestellt wird (einschließlich Vervollständigung mit der TAB-TASTE, Argumentkonvertierung und Unterstützung für automatisierte Tests, um nur einige zu nennen), dass Sie mit wenig Aufwand eine voll funktionsfähige Befehlszeilenunterstützung in allen Ihren dotnet-Anwendungen erhalten können.

Schließlich ist System.CommandLine eine Open-Source-Anwendung. Dies bedeutet, dass Sie die Verbesserung entwickeln und als Pull Request an die Community zurücksenden können, wenn die von Ihnen benötigte Funktionalität fehlt. Eine Funktion, die ich persönlich gerne hinzugefügt sehen würde, ist Unterstützung dafür, dass die Options- oder Befehlsnamen nicht immer in der Befehlszeile angegeben werden müssen und stattdessen die Position der Argumente zum Angeben der Namen verwendet wird. Außerdem wäre es hilfreich, wenn man der Verwendung des Ansatzes in der Art einer Main-Methode oder des Method-First-Ansatzes einen zusätzlichen Alias (z.B. kurze Aliase) deklarativ hinzufügen könnte.


Mark Michaelis ist der Gründer von IntelliTect und arbeitet als leitender technischer Architekt und Trainer. Seit fast zwei Jahrzehnten ist er ein Microsoft MVP und seit 2007 Microsoft-Regionalleiter. Michaelis arbeitet in verschiedenen Microsoft-Softwareentwicklungs-Reviewteams mit, einschließlich C#, Microsoft Azure, SharePoint und Visual Studio ALM. Er hält häufig Vorträge bei Entwicklerkonferenzen und hat viele Bücher geschrieben, einschließlich seines letzten „Essential C# 7.0 (6th Edition)“ (itl.tc/­EssentialCSharp). Sie können ihn auf Facebook unter facebook.com/Mark.Michaelis, über seinen Blog unter IntelliTect.com/Mark, auf Twitter: @markmichaelis oder per E-Mail unter mark@IntelliTect.com erreichen.

Unser Dank gilt den folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: Kevin Bost, Kathleen Dollard, Jon Sequeira