命令行参数是应用程序与用户交互的重要方式之一,它允许用户在启动程序时配置其行为。本文将全面介绍C#中解析命令行参数的各种方法,从基础API到现代高级库的使用。
一、命令行参数基础
1.1 命令行参数格式
常见的命令行参数风格:
风格类型 | 示例 | 特点 |
---|---|---|
POSIX风格 | -v --input file.txt | Unix/Linux系统常用 |
Windows风格 | /v /input:file.txt | 传统Windows程序常用 |
简单风格 | file.txt /s 100 | 无前缀,顺序敏感 |
1.2 Main方法接收参数
static void Main(string[] args)
{
// args数组包含所有命令行参数
foreach (var arg in args)
{
Console.WriteLine(arg);
}
}
示例调用:
dotnet run -- input.txt --output=result.txt -v
二、基础解析方法
2.1 手动解析简单参数
static void Main(string[] args)
{
string inputFile = null;
string outputFile = null;
bool verbose = false;
for (int i = 0; i < args.Length; i++)
{
switch (args[i])
{
case "-i":
case "--input":
inputFile = args[++i];
break;
case "-o":
case "--output":
outputFile = args[++i];
break;
case "-v":
case "--verbose":
verbose = true;
break;
default:
Console.WriteLine($"未知参数: {args[i]}");
break;
}
}
Console.WriteLine($"输入文件: {inputFile}");
Console.WriteLine($"输出文件: {outputFile}");
Console.WriteLine($"详细模式: {verbose}");
}
2.2 环境命令行获取
// 获取完整命令行(包括程序路径)
string fullCommandLine = Environment.CommandLine;
// 在Windows服务等环境中获取参数
string[] args = Environment.GetCommandLineArgs();
三、使用System.CommandLine(.NET 7+官方库)
3.1 基本配置
using System.CommandLine;
var rootCommand = new RootCommand("示例应用程序");
var inputOption = new Option<FileInfo>(
name: "--input",
description: "输入文件路径")
{
IsRequired = true
};
inputOption.AddAlias("-i");
var outputOption = new Option<FileInfo>(
name: "--output",
description: "输出文件路径");
outputOption.AddAlias("-o");
var verboseOption = new Option<bool>(
name: "--verbose",
description: "启用详细输出");
verboseOption.AddAlias("-v");
rootCommand.AddOption(inputOption);
rootCommand.AddOption(outputOption);
rootCommand.AddOption(verboseOption);
rootCommand.SetHandler((input, output, verbose) =>
{
Console.WriteLine($"处理文件: {input?.FullName}");
if (output != null)
Console.WriteLine($"输出到: {output.FullName}");
if (verbose)
Console.WriteLine("详细模式已启用");
}, inputOption, outputOption, verboseOption);
return rootCommand.Invoke(args);
3.2 子命令支持
var cloneCommand = new Command("clone", "克隆仓库")
{
new Argument<string>("url", "仓库URL"),
new Option<string>("--branch", "指定分支")
};
cloneCommand.SetHandler((url, branch) =>
{
Console.WriteLine($"克隆 {url} {(branch != null ? $"分支 {branch}" : "")}");
},
cloneCommand.Arguments[0], cloneCommand.Options[0]);
var rootCommand = new RootCommand();
rootCommand.AddCommand(cloneCommand);
return rootCommand.Invoke(args);
四、流行第三方库解析
4.1 CommandLineParser
dotnet add package CommandLineParser
using CommandLine;
class Options
{
[Option('i', "input", Required = true, HelpText = "输入文件路径")]
public string InputFile { get; set; }
[Option('o', "output", HelpText = "输出文件路径")]
public string OutputFile { get; set; }
[Option('v', "verbose", HelpText = "详细输出")]
public bool Verbose { get; set; }
}
static void Main(string[] args)
{
Parser.Default.ParseArguments<Options>(args)
.WithParsed(options =>
{
Console.WriteLine($"输入: {options.InputFile}");
if (!string.IsNullOrEmpty(options.OutputFile))
Console.WriteLine($"输出: {options.OutputFile}");
if (options.Verbose)
Console.WriteLine("详细模式");
})
.WithNotParsed(errors =>
{
Console.WriteLine("参数解析错误");
});
}
4.2 McMaster.Extensions.CommandLineUtils
dotnet add package McMaster.Extensions.CommandLineUtils
using McMaster.Extensions.CommandLineUtils;
[Command(Name = "myapp", Description = "文件处理工具")]
[HelpOption("--help")]
class Program
{
[Argument(0, Description = "输入文件")]
[FileExists]
public string InputFile { get; }
[Option("-o|--output", Description = "输出文件")]
public string OutputFile { get; }
[Option("-v|--verbose", Description = "详细输出")]
public bool Verbose { get; }
public static int Main(string[] args)
=> CommandLineApplication.Execute<Program>(args);
private void OnExecute()
{
if (Verbose)
Console.WriteLine($"处理 {InputFile}...");
// 处理逻辑
if (!string.IsNullOrEmpty(OutputFile))
{
if (Verbose)
Console.WriteLine($"写入 {OutputFile}");
// 写入输出
}
}
}
五、高级解析场景
5.1 数组参数
// System.CommandLine方式
var filesOption = new Option<FileInfo[]>(
name: "--files",
description: "要处理的文件列表")
{
AllowMultipleArgumentsPerToken = true
};
// CommandLineParser方式
[Option('f', "files", HelpText = "文件列表")]
public IEnumerable<string> Files { get; set; }
5.2 枚举参数
// 定义枚举
enum LogLevel { Quiet, Normal, Verbose }
// System.CommandLine
var logLevelOption = new Option<LogLevel>(
name: "--log-level",
description: "日志级别");
// CommandLineParser
[Option('l', "log-level", Default = LogLevel.Normal)]
public LogLevel Level { get; set; }
5.3 交互式提示
// McMaster.Extensions.CommandLineUtils
[Option]
[Prompt("请输入用户名")]
public string Username { get; set; }
[Option]
[Prompt("请输入密码", PromptSecret = true)]
public string Password { get; set; }
六、验证与错误处理
6.1 参数验证
// System.CommandLine验证
var portOption = new Option<int>("--port")
{
IsRequired = true
};
portOption.AddValidator(result =>
{
var port = result.GetValueForOption(portOption);
if (port < 1 || port > 65535)
result.ErrorMessage = "端口必须在1-65535之间";
});
// CommandLineParser验证
[Option('p', "port", Required = true)]
[Range(1, 65535, ErrorMessage = "端口无效")]
public int Port { get; set; }
6.2 自定义错误处理
Parser.Default.ParseArguments<Options>(args)
.WithNotParsed(errors =>
{
if (errors.IsVersion())
{
Console.WriteLine("版本信息");
return;
}
if (errors.IsHelp())
{
DisplayHelp();
return;
}
foreach (var error in errors)
{
switch (error)
{
case MissingRequiredOptionError missing:
Console.WriteLine($"缺少必要选项: {missing.NameInfo.NameText}");
break;
case UnknownOptionError unknown:
Console.WriteLine($"未知选项: {unknown.Token}");
break;
default:
Console.WriteLine($"参数错误: {error.Tag}");
break;
}
}
});
七、帮助系统与文档生成
7.1 自动帮助生成
// System.CommandLine自动支持
// 用户输入 --help 或 -? 时显示帮助
// McMaster.Extensions.CommandLineUtils
[HelpOption("--help")]
class Program
{
// ...
}
7.2 自定义帮助文本
var rootCommand = new RootCommand
{
Description = "文件处理工具 v1.0\nCopyright (c) 2023"
};
// 添加自定义帮助选项
var helpOption = new Option<bool>("--help", "显示帮助信息");
rootCommand.AddOption(helpOption);
rootCommand.SetHandler((help) =>
{
if (help)
Console.WriteLine("自定义帮助文本...");
}, helpOption);
八、性能优化
8.1 延迟解析
// 只在需要时解析
private static Lazy<Options> _options = new Lazy<Options>(() =>
Parser.Default.ParseArguments<Options>(args).Value);
public static Options Config => _options.Value;
8.2 缓存解析结果
private static Options _cachedOptions;
public static Options GetOptions(string[] args)
{
if (_cachedOptions == null)
{
_cachedOptions = Parser.Default.ParseArguments<Options>(args).Value;
}
return _cachedOptions;
}
九、跨平台注意事项
9.1 路径参数处理
// 统一路径格式
string inputPath = args[0].Replace('\\', Path.DirectorySeparatorChar);
// 使用Path组合
string fullPath = Path.Combine(
Environment.CurrentDirectory,
args[0].TrimStart('.', '/', '\\'));
9.2 大小写敏感
// Linux/macOS下区分大小写
bool verbose = args.Any(a =>
a.Equals("-v", StringComparison.OrdinalIgnoreCase) ||
a.Equals("--verbose", StringComparison.OrdinalIgnoreCase));
十、测试策略
10.1 单元测试参数解析
[TestClass]
public class ArgumentTests
{
[TestMethod]
public void TestBasicArguments()
{
var args = new[] { "--input", "test.txt", "--verbose" };
var result = Parser.Default.ParseArguments<Options>(args);
Assert.IsFalse(result.Errors.Any());
Assert.AreEqual("test.txt", result.Value.InputFile);
Assert.IsTrue(result.Value.Verbose);
}
[TestMethod]
public void TestMissingRequiredArgument()
{
var args = new[] { "--verbose" };
var result = Parser.Default.ParseArguments<Options>(args);
Assert.IsTrue(result.Errors.Any(e => e is MissingRequiredOptionError));
}
}
10.2 模拟命令行
[TestMethod]
public void TestCommandLineApp()
{
using var output = new StringWriter();
Console.SetOut(output);
var args = new[] { "clone", "https://github.com/user/repo", "--branch", "main" };
Program.Main(args);
StringAssert.Contains(output.ToString(), "克隆 https://github.com/user/repo 分支 main");
}
十一、最佳实践总结
- 选择合适库:
- 简单应用:System.CommandLine或内置解析
- 复杂CLI:CommandLineParser或McMaster.Extensions
- 良好文档:
- 为所有参数提供清晰的帮助文本
- 包含使用示例
- 输入验证:
- 尽早验证参数有效性
- 提供有意义的错误信息
- 一致性:
- 保持参数命名风格一致
- 遵循平台惯例(POSIX/Windows)
- 用户友好:
- 提供合理的默认值
- 支持–help和–version等标准选项
十二、进阶资源
- 官方文档:
- 设计指南:
- 示例项目:
通过本文的介绍,您应该能够根据项目需求选择合适的命令行参数解析方法,并实现健壮、用户友好的命令行界面。良好的命令行设计可以显著提升应用程序的易用性和专业性。