应改用 System.CommandLine,因 CommandLineParser 已归档停更,不支持 .NET 6+、无子命令/补全/国际化,解析行为不一致且错误提示不友好;迁移只需替换解析逻辑,注意参数传递、子命令定义和验证注册方式。

别用 CommandLineParser 了,它已归档停更,.NET 5+ 场景下应直接切换到 System.CommandLine。
为什么 CommandLineParser 不该再被选用
这个库早在 2021 年就进入归档状态(GitHub 仓库 marked as archived),NuGet 包最后更新是 2020 年;它不支持 .NET 6+ 的泛型主机、Microsoft.Extensions.DependencyInjection 集成,也没有子命令嵌套、自动补全、国际化等现代 CLI 必备能力。更关键的是:它解析 --config path.json 和 --config=path.json 时行为不一致,且对引号包裹含空格路径(如 "C:\My App\cfg.json")的处理依赖 shell 层,容易在不同终端出错。
- 官方明确推荐迁移路径:微软文档和
dotnet new模板默认都指向System.CommandLine - 如果你正在维护旧项目并遇到
ParserSettings编译失败、VerbAttribute不识别等问题,基本可以判定是版本兼容断裂 - 它的异常提示全是英文堆栈,没有上下文友好的错误文案(比如“--port must be between 1024 and 65535”这种)
System.CommandLine 替代 CommandLineParser 的最小可行写法
不需要重写整个入口逻辑,只需替换解析部分。假设你原来用 CommandLineParser 解析一个 --input 和 --verbose:
var result = Parser.Default.ParseArguments<Options>(args);
if (result is Parsed<Options> parsed)
{
Run(parsed.Value);
}
换成 System.CommandLine 后,等价实现是:
var rootCommand = new RootCommand("My tool");
var inputOption = new Option<string>("--input", "Input file path");
var verboseOption = new Option<bool>("--verbose", "Enable verbose output");
rootCommand.AddOption(inputOption);
rootCommand.AddOption(verboseOption);
rootCommand.Handler = CommandHandler.Create<string, bool>((input, verbose) =>
{
Run(input, verbose);
});
return await rootCommand.InvokeAsync(args);
-
Option<t></t>自动处理--input=file.txt、--input file.txt、-i file.txt三种写法 - 类型
bool选项天然支持开关语义:写--verbose就是true,不写就是false,无需额外判断 - 如果
input是必需参数,加一句inputOption.IsRequired = true;,缺参数时会自动输出错误并退出
从 CommandLineParser 迁移时最常踩的坑
不是语法不会写,而是旧习惯导致新行为出错:
- 别再用
Environment.GetCommandLineArgs()—— 它返回的数组第一个元素是程序全路径,而System.CommandLine严格只认Main(string[] args)的参数,混用会导致解析偏移 - 子命令迁移要重定义结构:原来
[Verb("build")]对应的类,现在得新建new Command("build")并挂载到RootCommand上,不能靠反射自动发现 - 自定义验证逻辑不能写在属性上(像
[Validator("...")]),得用option.AddValidator(...)注册委托,否则验证不触发 - 帮助文本(
-h/--help)是自动绑定的,但如果你手动调用了Console.WriteLine输出 help,记得删掉,否则会重复打印
真正麻烦的从来不是“怎么写”,而是旧项目里那些散落在 Program.cs 外部、靠静态字段传递参数的 hack 写法——这些必须收口到 Handler 方法签名里,否则类型安全就断了。


















