Main() 与命令行参数

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

  1. Main 的声明规则:必须 static、8 种有效签名的完整清单
  2. 返回值与退出码 :int 和 Task<int> 如何向调用方传递状态
  3. 命令行参数 :string[] args 的使用方式与 GetCommandLineArgs() 的区别
  4. 异步 Main :async 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 参数
  • 不需要退出码 → 用 void 或 Task
  • 需要 await → 用 async Task 或 async 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 int​ :async 修饰符要求返回类型是可等待的(Task 或 Task<int>),void 和 int 不是
  3. ​return await ...​ :把异步工作的结果作为进程退出码返回

四、命令行参数

4.1 基本用法

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

关键特性:

  • args 是 string[],永不为 ​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 表示成功。

‍