Main() 与命令行参数

很多现有代码库仍使用显式 Main​ 方法作为入口点。这篇把 Main​ 的所有有效签名 、返回值规则、命令行参数处理以及异步 Main 的用法完整梳理一遍。

  1. Main 的声明规则:必须 static、8 种有效签名的完整清单
  2. 返回值与退出码intTask<int> 如何向调用方传递状态
  3. 命令行参数string[] args 的使用方式与 GetCommandLineArgs() 的区别
  4. 异步 Mainasync Task Main 的规则和陷阱

一、Main 方法的基本规则

Main​ 是 C# 程序的入口点。程序启动时,运行时先调用 Main​,Main 返回后程序结束。

规则 说明
声明位置 必须在class​或struct​内(封闭类可以是static
修饰符 必须 static
访问级别 任意访问修饰符均可(默认private
返回类型 void​、int​、Task​或Task<int>
参数 可选string[] args
唯一入口点 一个程序只能有一个入口点;多Main​时需用-main编译器选项指定

二、所有有效的 Main 签名

csharp 复制代码
 // 无参数、无异步、无返回码
 static void Main() { }
 static int Main() { }
 ​
 // 带命令行参数
 static void Main(string[] args) { }
 static int Main(string[] args) { }
 ​
 // 异步
 static async Task Main() { }
 static async Task<int> Main() { }
 ​
 // 异步 + 命令行参数
 static async Task Main(string[] args) { }
 static async Task<int> Main(string[] args) { }

完整功能矩阵

Main 声明 string[] args await 退出码
static void Main()
static int Main()
static void Main(string[] args)
static int Main(string[] args)
static async Task Main()
static async Task<int> Main()
static async Task Main(string[] args)
static async Task<int> Main(string[] args)

选择策略:

  • 不需要 args → 省略 string[] args 参数
  • 不需要退出码 → 用 voidTask
  • 需要 await → 用 async Taskasync Task<int>
  • 全都要 → static async Task<int> Main(string[] args)

关键点: async void Main​ 是非法的。异步 Main​ 必须返回 Task​ 或 Task<int>​,因为运行时需要等待 Task 完成后才结束进程。

三、返回值与退出状态码

返回 int​ 或 Task<int> 时,程序可以向调用方(其他程序或脚本)传递状态信息:

csharp 复制代码
 class MainReturnValTest
 {
     static int Main()
     {
         // ... 业务逻辑 ...
         return 0;  // 0 = 成功,非零 = 错误
     }
 }

约定:

  • return 0 表示成功
  • return 非零值 表示错误(具体值由程序自定义)

如何获取退出码:

环境 方式
PowerShell $LastExitCode
CMD / 批处理 %ERRORLEVEL%
Linux / macOS Shell $?

异步 Main 的返回值

csharp 复制代码
 class Program
 {
     static async Task<int> Main(string[] args)
     {
         return await AsyncConsoleWork();
     }
 ​
     private static async Task<int> AsyncConsoleWork()
     {
         // 异步工作...
         return 0;
     }
 }

代码解析:

  1. async Task<int> Main :运行时调用 Main 后,等待返回的 Task 完成才结束进程
  2. 不能返回 async void async intasync 修饰符要求返回类型是可等待的(TaskTask<int>),voidint 不是
  3. return await ... :把异步工作的结果作为进程退出码返回

四、命令行参数

4.1 基本用法

csharp 复制代码
 static void Main(string[] args)
 {
     Console.WriteLine(args.Length);
 }

关键特性:

  • argsstring[]永不为 null
  • 未提供参数时,args.Length == 0(空数组)
  • 参数是零索引 的:args[0] 是第一个参数

4.2 与 C/C++ 的区别

特性 C# C/C++
args[0] 第一个命令行参数 程序名称
获取程序名 Environment.GetCommandLineArgs()[0] argv[0]

常见坑: 从 C/C++ 转过来的开发者容易以为 args[0]​ 是程序名,实际不是。用 Environment.GetCommandLineArgs()[0]​ 或 Process.GetCurrentProcess().MainModule.FileName 获取。

4.3 参数类型转换

csharp 复制代码
 long num = long.Parse(args[0]);
 int count = int.Parse(args[1]);
 double price = double.Parse(args[2]);

建议: 手动 Parse​ 适合简单场景。复杂的命令行参数(如 --name value --verbose​)考虑使用 System.CommandLine 库。

4.4 完整示例

csharp 复制代码
 class TestClass
 {
     static void Main(string[] args)
     {
         // 显示参数数量
         Console.WriteLine($"参数数量: {args.Length}");
 ​
         // 遍历参数
         for (int i = 0; i < args.Length; i++)
         {
             Console.WriteLine($"args[{i}] = {args[i]}");
         }
 ​
         // 用 GetCommandLineArgs 获取完整命令行(含程序名)
         string[] allArgs = Environment.GetCommandLineArgs();
         Console.WriteLine($"程序名: {allArgs[0]}");
     }
 }

五、顶级语句 vs 显式 Main 对照

需求 顶级语句写法 显式 Main 写法
打印 args 数量 Console.WriteLine(args.Length); static void Main(string[] args) { Console.WriteLine(args.Length); }
异步 await Task.Delay(1000); static async Task Main() { await Task.Delay(1000); }
返回退出码 return 0; static int Main() { return 0; }
异步 + 退出码 await ...; return 0; static async Task<int> Main() { await ...; return 0; }

最后

如果从零开始建新项目,直接上顶级语句 会更简洁。但如果你在维护一个使用显式 Main​ 的旧项目,或者需要精确控制入口点的多 Main​ 选择(-main​ 编译器选项),这篇就是你的完整参考。记住三条:async void Main​ 非法、args 永不为 null、退出码 0 表示成功。