5.4 Main()的返回值和参数
到目前为止,可执行体的所有Main()方法采用的都是最简单的声明。这些Main()方法声明不包含任何参数或非void返回类型。但C#支持在执行程序时提供命令行参数,并允许从Main()方法返回状态标识符。
“运行时”通过一个string数组参数将命令行参数传给Main()。要获取参数,访问数组就可以了,代码清单5.12对此进行了演示。程序的目的是下载指定URL位置的文件。第一个命令行参数指定URL,第二个指定存盘文件名。代码从一个switch语句开始,根据参数数量(args.Length)采取不同操作:
1.如果没有两个参数,就显示一条错误消息,指出必须提供URL和文件名;
2.如果有两个参数,表明用户提供了URL和存盘文件名。
代码清单5.12 向Main()传递命令行参数
代码清单5.12的结果如输出5.4所示。
输出5.4
成功获取存盘文件名,就用它保存下载的文件。否则应显示帮助文本。Main()方法还会返回一个int,而不是像往常那样返回void。返回值对于Main()声明来说是可选的。但如果有返回值,程序就可以将状态码返回给调用者(比如脚本或批处理文件)。根据约定,非零返回值代表出错。
虽然所有命令行参数都可通过字符串数组传给Main(),但有时需要从非Main()的方法中访问参数。这时可用System.Environment.GetCommandLineArgs()方法以Main (string[]args)将参数传递到Main()的相同形式返回由命令行参数构成的数组。
高级主题:消除多个Main()方法的歧义
假如一个程序的两个类都有Main()方法,可以选取其中一个类作为程序的入口点。在Visual Studio中找到“解决方法资源管理器”面板,右键单击项目图标,在弹出的菜单中点击“属性”命令。属性设置页面将会显示在窗口右侧。请确保“应用程序”选项卡处于选中状态。在页面中找到“启动对象”下拉选单。在这里可以选择作为入口点的类名称。如果使用命令行编译程序,也可以通过设置StartupObject属性来设置入口点的类。例如:
上例中,AddisonWesley.Program2是具有Main()方法的命名空间和类名称。
初学者主题:调用栈和调用点
代码执行时,方法可能调用其他方法,其他方法可能调用更多方法,以此类推。在代码清单5.4的简单情况中,Main()调用GetUserInput(),后者调用System.Console.ReadLine(),后者又在内部调用更多方法。每次调用新方法时,“运行时”都创建一个“栈帧”或“活动帧”,其中包含的内容涉及传给新调用的实参、新调用的局部变量以及方法返回时应该从哪里恢复等。这样形成的一系列栈帧称为调用栈[1]。随着程序复杂度的提高,每个方法调用另一个方法时,这个调用栈都会变大。但当调用结束时,调用栈会发生收缩,直到调用另一个方法。我们用栈展开(stack unwinding)[2]一词描述从调用栈中删除栈帧的过程。栈展开的顺序通常与方法调用的顺序相反。方法调用完毕,控制会返回调用点(call site),也就是最初发出方法调用的位置。
[1] async或迭代器方法除外,它们的活动记录转移到堆上。
[2] unwind一般翻译成“展开”,但这并不是一个很好的翻译。wind和unwind源于生活。把线缠到线圈上称为wind;把线从线圈上松开称为unwind。同样地,调用方法时压入栈帧,称为wind;方法执行完毕弹出栈帧,称为unwind。——译者注