CLI приложения #3. System.CommandLine: то же самое, но по-взрослому
В прошлый раз мы парсили команды, опции и аргументы вручную. Много строк и скудный функционал. Теперь разберём использование библиотеки.
Подключаем:
# можно в графической оболочке управления пакетами, но так не спортивно ))
dotnet add package System.CommandLine
Важно!
В ноябре 2025 года библиотека вышла из беты и внутри значительно изменился
API, а примеры, которые можно найти в гайдах написаны по большей части на ней (до обновления, а некоторые и после). Код из старых гайдов не соберется! Так что устанавливаем версии2.0и выше.
Идём дальше. Основных сущностей при работе с CLI приложениями три (а можно посчитать за 4 ):

Что есть что в CLI приложении#
Теперь идём в VS и пишем код:
using System.CommandLine;
internal class Program
{
private static int Main(string[] args)
{
var nameOption = new Option<string>("--name", "-n")
{
Description = "Имя для приветствия",
Required = true
};
var countOption = new Option<int>("--count", "-c")
{
Description = "Сколько раз поздороваться",
DefaultValueFactory = _ => 1
};
var verboseOption = new Option<bool>("--verbose", "-v")
{
Description = "Подробный вывод"
};
var rootCommand = new RootCommand("Приложение здоровается с пользователем");
rootCommand.Options.Add(nameOption);
rootCommand.Options.Add(countOption);
rootCommand.Options.Add(verboseOption);
rootCommand.SetAction(parseResult =>
{
var name = parseResult.GetValue(nameOption);
var count = parseResult.GetValue(countOption);
var verbose = parseResult.GetValue(verboseOption);
for (int i = 0; i < count; i++)
Console.WriteLine($"Привет, {name}!");
if (verbose)
Console.WriteLine($"Готово, повторов: {count}");
return 0;
});
return rootCommand.Parse(args).Invoke();
}
}
И можно запускать.
- Как в прошлый раз:
dotnet run -- --name Иван --count 3 -v
- То, чего там не было:
dotnet run -- --help
Справка сгенерирована автоматически из описания опций, которые мы дали в коде.
Забегая наперёд скажу, что в этих местах, когда пишется мультиязычное приложение (если вдруг) используются строки из ресурсов. Тогда можно подставлять описания на разных языках в зависимости от локали.
Что ещё заработало само, без единой строки нашего кода:
--count=3— та самая форма, которую в прошлый раз пришлось писать отдельной веткой, и только для одного параметра--count абввместо числа выдаст понятную ошибку про неверный формат, а не просто ноль- без
--nameприложение сообщит, что обязательный параметр не указан — за это отвечаетRequired = true --как разделитель, после которого всё считается значениями
Про последний пункт: Когда мы пишем dotnet run -- --name Иван, эти два дефиса нужны именно затем, чтобы dotnet run забрал себе то, что слева, а нашему приложению отдал то, что справа. Без них -v уедет в dotnet как уровень детализации сборки, и вы получите ошибку от неё, а не от своей программы.
Кастомные проверки значений добавляются не менее просто, чем сами опции:
countOption.Validators.Add(result =>
{
if (result.GetValueOrDefault<int>() < 1)
result.AddError("Количество должно быть больше нуля");
});
В прошлый раз нужно было добавить отдельный if и в конечном итоге при десятке опций получили бы нечитаемую кашу из if-ов.
Итого.
С библиотекой кода меньше, читаемость выше, а поведение стандартное для любого CLI приложения.
Сейчас наше приложение умеет выполнять всего одну операцию. Во взрослых CLI всё несколько сложнее, например git commit, docker run, dotnet build - уже используют подкоманды. Ими мы займёмся в следующий раз.