Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Tipp
Dieser Artikel ist Teil des Abschnitts "Grundlagen" , der für Entwickler geschrieben wurde, die mindestens eine Programmiersprache kennen und C# erlernen. Wenn Sie noch nicht mit der Programmierung vertraut sind, beginnen Sie mit "Erste Schritte". Wenn Sie eine umfassende Bibliotheksabdeckung benötigen, lesen Sie die Dokumentation zur System.CommandLine-Bibliothek.
Die System.CommandLine Bibliothek behandelt die Befehlszeilenanalyse, die Hilfetextgenerierung und die Eingabeüberprüfung, sodass Sie sich auf die Logik Ihrer App konzentrieren können. In diesem Lernprogramm erstellen Sie eine Aufgabenverfolgungs-CLI, die die Kernkonzepte veranschaulicht: Befehle, Unterbefehle, Optionen und Argumente.
In diesem Tutorial lernen Sie Folgendes:
- Erstellen Sie eine dateibasierte App mithilfe des
System.CommandLinePakets. - Definieren Sie Optionen und Argumente mit eingegebenen Werten.
- Erstellen Von Unterbefehlen und Anfügen von Optionen und Argumenten.
- Behandeln Sie jeden Unterbefehl mit einer Aktion.
- Testen Sie die App mit unterschiedlichen Befehlszeileneingaben.
Voraussetzungen
- Installieren Sie das .NET 10 SDK oder höher.
Erstellen der App
Erstellen Sie zunächst ein dateibasiertes C#-Programm, und fügen Sie das System.CommandLine Paket hinzu.
Erstellen Sie eine Datei mit dem Namen
TaskCli.csund dem folgenden Inhalt:#!/usr/bin/env dotnetMit
#!der Zeile (Shebang) können Sie die Datei direkt auf Unix-Systemen ausführen. Führen Sie unter Windows die Datei mitdotnet run TaskCli.cs.Fügen Sie die
System.CommandLinePaketdirektive und die erforderlichenusingAnweisungen hinzu:#:package System.CommandLine@2.0.0using System.CommandLine; using System.CommandLine.Parsing; using System.Text.Json;Von Bedeutung
Version
2.0.0ist die neueste Version zum Zeitpunkt des Schreibens. Überprüfen Sie die NuGet-Seite des Pakets auf die neueste Version, um sicherzustellen, dass Sie über die neuesten Sicherheitsupdates verfügen.
Befehlsstruktur verstehen
Berücksichtigen Sie vor dem Schreiben von Analysecode, wie die CLI aus Sicht des Benutzers aussieht. Die Aufgabenverfolgung unterstützt vier Vorgänge:
dotnet TaskCli.cs -- add "Write documentation" --priority High --due 2026-04-01
dotnet TaskCli.cs -- list --all
dotnet TaskCli.cs -- complete 3
dotnet TaskCli.cs -- remove 3
dotnet TaskCli.cs -- --verbose list
Hinweis
Nach dem -- in den vorhergehenden Beispielen wird dotnet run angezeigt, dass alle verbleibenden Argumente an Ihre App übergeben werden, anstatt von der dotnet CLI selbst interpretiert zu werden.
Jede Zeile verwendet mehrere Befehlszeilenkonzepte:
-
Unterbefehle sind Verben , die der App mitteilen, was zu tun ist. Die Aufgabenverfolgung hat vier:
add,list, ,completeundremove. Jeder Unterbefehl kann eigene Parameter definieren. -
Argumente sind Positionswerte, die einem Unterbefehl folgen. In
add "Write documentation", die Zeichenfolge"Write documentation"ist ein Argument, das die Aufgabenbeschreibung angibt. Incomplete 3, die Zahl3ist ein Argument, das die Vorgangs-ID angibt. -
Optionen sind benannte Werte, die mit
--vorangestellt sind. Inadd --priority High --due 2026-04-01sind--priorityund--dueOptionen mit ihren eigenen Werten. Inlist --allist die--all-Option ein boolesches Flag, das keinen Wert benötigt. -
Globale Optionen gelten für jeden Unterbefehl. Die
--verboseOption wird für den Stammbefehl mitRecursive = truedefiniert, sodass sie mit jedem Unterbefehl funktioniert. In--verbose list, die ausführliche Kennzeichnung wird vor dem Unterbefehl angezeigt, funktioniert aberlist --verbosegenauso gut.
In den folgenden Abschnitten erstellen Sie diese Teile von unten nach oben. Zunächst definieren Sie die einzelnen Optionen (wie --priority und --all) sowie die Argumente (z. B. die Aufgabenbeschreibung und ID). Als Nächstes erstellen Sie die vier Unterbefehle, und fügen Sie die relevanten Optionen und Argumente jeweils an. Dann verknüpfen Sie eine Aktion mit jedem Unterbefehl. Die Aktion ist der Code, der ausgeführt wird, wenn der Benutzer diesen Befehl aufruft. Schließlich erstellen Sie den Stammbefehl, analysieren die Eingabe und rufen die übereinstimmene Aktion auf.
Einen tieferen Einblick in die Konzepte der Befehlszeilensyntax finden Sie in der Übersicht über die Befehlszeilensyntax.
Definieren von Optionen und Argumenten
Optionen stellen benannte Werte dar, die Benutzer mit einem -- Präfix angeben. Argumente stellen Positionswerte dar. Beide sind stark typisiert.
System.CommandLine analysiert die Eingabezeichenfolge in den angegebenen Typ.
Die System.CommandLine Bibliothek verwendet generische Typen , um die Typsicherheit zu erzwingen. Beim Schreiben Option<int> ist der int Inhalt zwischen den spitzen Klammern ein Typargument. Sie teilt der Bibliothek mit, welche Art von Wert die Option enthält. Die Klasse selbst deklariert einen TypparameterT (wie in System.CommandLine.Option<T>), und Sie geben den konkreten Typ an, wenn Sie eine Instanz erstellen. Die Bibliothek analysiert die Eingabezeichenfolge des Benutzers und konvertiert sie automatisch in diesen Typ. Wenn der Benutzer --delay abc für ein Option<int> bereitstellt, meldet System.CommandLine einen Analysefehler, anstatt fehlerhafte Daten an Ihren Code zu übergeben. Dieses Muster wird mit Option<bool>, Option<Priority>, Option<DateOnly?>, und System.CommandLine.Argument<T> in den folgenden Schritten angezeigt. Weitere Informationen zu Generika finden Sie unter Generics.
Definieren Sie die Optionen. Jeder
Option<T>gibt den Werttyp, den Namen und eine Beschreibung an. Die--priorityOption verwendet einenenumTyp undSystem.CommandLineüberprüft die Eingabe automatisch anhand gültiger Enumerationswerte:var verboseOption = new Option<bool>("--verbose") { Description = "Show detailed output", Recursive = true }; var priorityOption = new Option<Priority>("--priority") { Description = "Task priority level", DefaultValueFactory = _ => Priority.Medium }; var dueOption = new Option<DateOnly?>("--due") { Description = "Due date (uses current culture date format)" }; var allOption = new Option<bool>("--all") { Description = "Include completed tasks" };Die
Recursive = trueEinstellung stellt--verbosedie Option für jeden Unterbefehl zur Verfügung. DerDefaultValueFactoryauf--prioritybietet einen Standardwert, sodass Benutzer die Option weglassen können.Definieren Sie die Argumente. Jeder
Argument<T>gibt den Werttyp und einen Namen an:var descriptionArgument = new Argument<string>("description") { Description = "Task description" }; var taskIdArgument = new Argument<int>("id") { Description = "Task ID" };
Erstellen von Befehlen und Unterbefehlen
A System.CommandLine.Command stellt eine Aktion dar, die der Benutzer aufrufen kann. Fügen Sie jedem Befehl die relevanten Optionen und Argumente hinzu, damit System.CommandLine erkennt, welche Parameter wo hingehören.
Erstellen Sie die vier Unterbefehle. Jeder Befehl erhält eine eigene Kombination aus Optionen und Argumenten:
var addCommand = new Command("add", "Add a new task") { Arguments = { descriptionArgument }, Options = { priorityOption, dueOption } };var listCommand = new Command("list", "List all tasks") { Options = { allOption } };var completeCommand = new Command("complete", "Mark a task as complete") { Arguments = { taskIdArgument } };var removeCommand = new Command("remove", "Remove a task") { Arguments = { taskIdArgument } };Den Root-Befehl zusammenstellen. Dies System.CommandLine.RootCommand ist der Einstiegspunkt für die CLI. Fügen Sie die globale
--verboseOption und alle Unterbefehle hinzu:var rootCommand = new RootCommand("A simple task tracker CLI") { Options = { verboseOption }, Subcommands = { addCommand, listCommand, completeCommand, removeCommand } };Der automatisch generierte Hilfetext zeigt die Beschreibung des Root-Befehls an, wenn der Benutzer
TaskCli --helpausführt.
Behandeln von Befehlen mit Aktionen
Jeder Unterbefehl benötigt eine Aktion. Eine Aktion ist ein Delegat, der ausgeführt wird, wenn der Benutzer diesen Befehl aufruft. Ein Delegat ist ein Typ, der einen Verweis auf eine Methode darstellt. Hier übergeben Sie einen Lambda-Ausdruck (eine inline anonyme Funktion, die mit => definiert wird) als Delegat. Rufen Sie SetAction auf, um jede Aktion zuzuweisen. Der Delegat erhält ein ParseResult, das Zugriff auf analysierte Werte durch GetValue ermöglicht.
Legen Sie die Aktion für den
addBefehl fest. Diese Aktion führt die Zeichenfolgeninterpolation ein ($"..."Zeichenfolgen, die Ausdrücke in geschweifte Klammern einbetten), den bedingten Operator () und ein?:(due is DateOnly dueDate), mit dem überprüft wird, ob ein nullabler Wert einen Wert aufweist und einer neuen Variablen in einem Schritt zuweist:addCommand.SetAction(parseResult => { var description = parseResult.GetValue(descriptionArgument)!; var priority = parseResult.GetValue(priorityOption); var due = parseResult.GetValue(dueOption); var verbose = parseResult.GetValue(verboseOption); var tasks = LoadTasks(); var id = tasks.Count > 0 ? tasks.Max(t => t.Id) + 1 : 1; var task = new TaskItem(id, description, priority, due, false); tasks.Add(task); SaveTasks(tasks); Console.WriteLine($"Added task {id}: {description}"); if (verbose) { Console.WriteLine($" Priority: {priority}"); if (due is DateOnly dueDate) { Console.WriteLine($" Due: {dueDate}"); } } });Legen Sie die Aktion für den
listBefehl fest. LINQ (Language Integrated Query) bietet Ihnen standardmäßige Abfrageoperatoren für Speicherauflistungen. Filtert in dieser Aktion die Aufgaben nur auf die Elemente, Enumerable.Where die einer Bedingung entsprechen, und Enumerable.ToList materialisiert die gefilterte Sequenz in eine Liste. Die Aktion verwendet dann eineforeachSchleife, um die Ergebnisse zu durchlaufen, und den bedingten Operator, um ein Statussymbol auszuwählen.listCommand.SetAction(parseResult => { var showAll = parseResult.GetValue(allOption); var verbose = parseResult.GetValue(verboseOption); var tasks = LoadTasks(); var filtered = showAll ? tasks : tasks.Where(t => !t.IsComplete).ToList(); if (filtered.Count == 0) { Console.WriteLine("No tasks found."); return; } foreach (var task in filtered) { var status = task.IsComplete ? "✓" : " "; Console.WriteLine($" [{status}] {task.Id}: {task.Description}"); if (verbose) { Console.WriteLine($" Priority: {task.Priority}"); if (task.Due is DateOnly dueDate) { Console.WriteLine($" Due: {dueDate}"); } } } });Weitere Details finden Sie unter LINQ.
Legen Sie die Aktion für den
completeBefehl fest. Diese Aktion verwendet LINQs Enumerable.FirstOrDefault , um Folgendes zu finden:- Eine Zuordnungsaufgabe.
- Ein
is nullMuster zum Überprüfen, ob die Aufgabe vorhanden ist. - Ein
withAusdruck zum Erstellen einer neuen Datensatzinstanz durch Kopieren der vorhandenen Werte zuerst und anschließendes Anwenden der Eigenschaften, diewithSie im Initialisierer festgelegt haben (hier,IsComplete = true). Datensätze sind standardmäßig unveränderlich, daher verwenden Sie dieses Kopier- und Aktualisierungsmuster, um einen geänderten Wert zu erzeugen.
Da die Aktion fehlschlagen kann (z. B. ist die Aufgaben-ID nicht vorhanden), gibt die Aktion einen ganzzahligen Fehlercode zurück, der zum Beendigungscode der App wird:
completeCommand.SetAction(parseResult => { var id = parseResult.GetValue(taskIdArgument); var verbose = parseResult.GetValue(verboseOption); var tasks = LoadTasks(); var task = tasks.FirstOrDefault(t => t.Id == id); if (task is null) { Console.Error.WriteLine($"Task {id} not found."); return -1; } tasks[tasks.IndexOf(task)] = task with { IsComplete = true }; SaveTasks(tasks); Console.WriteLine($"Completed task {id}: {task.Description}"); if (verbose) { Console.WriteLine($" Priority: {task.Priority}"); } return 0; });Legen Sie die Aktion für den
removeBefehl fest. Diese Aktion folgt demselben Nachschlage- und Überprüfungsmuster wiecomplete. Verwenden SieFirstOrDefault, um die Aufgabe zu finden, undis null, um den fehlenden Fall zu behandeln.removeCommand.SetAction(parseResult => { var id = parseResult.GetValue(taskIdArgument); var verbose = parseResult.GetValue(verboseOption); var tasks = LoadTasks(); var task = tasks.FirstOrDefault(t => t.Id == id); if (task is null) { Console.Error.WriteLine($"Task {id} not found."); return -1; } tasks.Remove(task); SaveTasks(tasks); Console.WriteLine($"Removed task {id}: {task.Description}"); if (verbose) { Console.WriteLine($" Priority: {task.Priority}"); } return 0; });Analysieren Sie die Befehlszeile, und rufen Sie die übereinstimmene Aktion auf:
return rootCommand.Parse(args).Invoke();rootCommand.Parse(args)analysiert die Eingabe in ein ParseResult, und.Invoke()führt die Aktion für den abgeglichenen Befehl aus. Der Rückgabewert ist ein Beendigungscode (0 für Erfolg).
Hinzufügen von unterstützenden Typen und Datenhilfsprogramme
Die App benötigt einige unterstützende Teile: lokale Funktionen, ein enum, ein record, und einen Serialisierungskontext. In den folgenden Abschnitten wird jedes Konzept vorgestellt, erläutert, warum Sie es auswählen und den Code anzeigen. Die App verwendet System.Text.Json zum Speichern von Aufgaben als JSON (JavaScript Object Notation). Dateibasierte Apps erfordern Typdeklarationen, die nach allen Anweisungen auf oberster Ebene und lokalen Funktionen angezeigt werden.
Fügen Sie die lokalen Funktionen hinzu, mit denen Aufgaben geladen und gespeichert werden. Eine lokale Funktion ist eine Methode, die innerhalb einer anderen Funktion deklariert wird, einschließlich innerhalb anderer lokaler Funktionen. Lokale Funktionen behalten hilfslogik in der Nähe des Codes, der ihn aufruft, wodurch die Lesbarkeit verbessert wird, da ein Leser nicht zu einer separaten Klasse oder Datei springen muss, um den Fluss zu verstehen.
LoadTasksundSaveTaskskapseln die Datei-E/A, die mehrere Befehlsaktionen gemeinsam nutzen, sodass die App die Lade-/Speicherlogik einmal schreibt und wiederverwendet:static string GetTaskFilePath() => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "taskcli-sample", "tasks.json"); static List<TaskItem> LoadTasks() { var path = GetTaskFilePath(); if (!File.Exists(path)) { return []; } var json = File.ReadAllText(path); return JsonSerializer.Deserialize(json, TaskJsonContext.Default.ListTaskItem) ?? []; } static void SaveTasks(List<TaskItem> tasks) { var path = GetTaskFilePath(); Directory.CreateDirectory(Path.GetDirectoryName(path)!); var json = JsonSerializer.Serialize(tasks, TaskJsonContext.Default.ListTaskItem); File.WriteAllText(path, json); }Fügen Sie
PriorityEnum undTaskItemRecord am Ende der Datei hinzu.public enum Priority { Low, Medium, High }Ein
enumWerttyp, der einen festen Satz benannter Konstanten definiert, die von einem integralen Typ unterstützt werden. Sie können Prioritätsebenen mit einfachen ganzzahligen Zahlen (0, 1, 2) darstellen, aber einenumist aus mehreren Gründen die bessere Wahl: Der Compiler schränkt Zuweisungen auf die definierten Namen ein, sodass ein Tippfehler wieHihgeinen Kompilierungsfehler anstelle eines stillen Fehlers verursacht; die NamenLow,MediumundHighmachen den Code besser lesbar; undSystem.CommandLineüberprüft die Benutzereingabe automatisch gegen die Enum-Mitglieder, was eine automatische Eingabeüberprüfung beinhaltet.public record TaskItem(int Id, string Description, Priority Priority, DateOnly? Due, bool IsComplete);A
recordist ein Typ, den der Compiler mit wertbasierter Gleichheit und nicht destruktiver Mutation ausstattet. Arecordist die richtige Lösung fürTaskItem, da Vorgangsdaten ein einfacher Zustand ohne komplexes Verhalten sind – Sie vergleichen Aufgaben anhand der Werte, nicht anhand der Referenzidentität. Der Compiler generiertEquals,GetHashCode,ToStringundwithAusdrucksunterstützung aus den Konstruktorparametern, sodass Sie korrekte Gleichheitsprüfungen, einfache Debugausgabe und unveränderliche Updates ohne Schreiben von Textbausteinen erhalten. Da Datensätze standardmäßig unveränderlich sind, verwenden Sie einenwithAusdruck, um eine modifizierte Kopie zu erzeugen (wie durch diecompleteAktion), anstatt das Original zu verändern, was unbeabsichtigte Nebenwirkungen verhindert, wenn verschiedene Aktionen dieselbe Liste schreiben und lesen.Fügen Sie den JSON-Serialisierungskontext für AOT-kompatible Serialisierung hinzu:
[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)] [System.Text.Json.Serialization.JsonSerializable(typeof(List<TaskItem>))] internal partial class TaskJsonContext : System.Text.Json.Serialization.JsonSerializerContext;Diese Klasse verwendet zwei Attribute (Metadatenanmerkungen, die in eckigen Klammern über einer Deklaration platziert werden). Das
[JsonSourceGenerationOptions(WriteIndented = true)]Attribut weist den Quellgenerator an, eingezogenen JSON zur Lesbarkeit zu senden. Das[JsonSerializable(typeof(List<TaskItem>))]Attribut teilt dem Generator mit, für welchen Typ Serialisierungscode erstellt werden soll. Gemeinsam ermöglichen diese Attribute die vom Quelle generierte Serialisierung, wodurch Laufzeitreflektionen vermieden und die AOT-Kompilierung (Ahead-of-Time) unterstützt wird.
Testen der App
Führen Sie die App mit unterschiedlichen Eingaben aus, um jeden Unterbefehl auszuführen.
Anzeigen der automatisch generierten Hilfe:
dotnet run TaskCli.cs -- --helpDescription: A simple task tracker CLI Usage: TaskCli [command] [options] Options: --verbose Show detailed output -?, -h, --help Show help and usage information --version Show version information Commands: add <description> Add a new task list List all tasks complete <id> Mark a task as complete remove <id> Remove a taskAufgaben mit verschiedenen Optionen hinzufügen:
dotnet run TaskCli.cs -- add "Write documentation" --priority High --due 2026-04-01 dotnet run TaskCli.cs -- add "Review pull request" dotnet run TaskCli.cs -- add "Fix build errors"Added task 1: Write documentation Added task 2: Review pull request Added task 3: Fix build errorsListen Sie Aufgaben auf, und verwenden
--verboseSie diese, um zusätzliche Details anzuzeigen:dotnet run TaskCli.cs -- --verbose list[ ] 1: Write documentation Priority: High Due: 4/1/2026 [ ] 2: Review pull request Priority: Medium [ ] 3: Fix build errors Priority: MediumFühren Sie eine Aufgabe aus und überprüfen Sie dann, ob die Listenfilter standardmäßig erledigte Aufgaben anzeigen:
dotnet run TaskCli.cs -- complete 2 dotnet run TaskCli.cs -- listCompleted task 2: Review pull request [ ] 1: Write documentation [ ] 3: Fix build errorsVerwenden Sie
--all, um abgeschlossene Aufgaben einzuschließen.dotnet run TaskCli.cs -- list --all[ ] 1: Write documentation [✓] 2: Review pull request [ ] 3: Fix build errorsEntfernen einer Aufgabe:
dotnet run TaskCli.cs -- remove 3Removed task 3: Fix build errors
Bereinigen von Ressourcen
Die Aufgabenverfolgung speichert Daten in einer JSON-Datei unter Ihrem lokalen Anwendungsdatenordner. Löschen Sie den taskcli-sample Ordner, um die Beispieldaten zu entfernen:
-
Windows: Löschen
%LOCALAPPDATA%\taskcli-sample. -
macOS: Delete
~/Library/Application Support/taskcli-sample. -
Linux: Delete
~/.local/share/taskcli-sample.