本节摘要:C# 程序由命名空间包着类型、类型包着成员构成三层结构。本节从编译单元视角讲清每层语法元素如何落进程序集元数据,顶级语句与入口方法的关系,以及命名规范背后的工程逻辑。
.NET 6 起新控制台工程的模板只剩一行:
Console.WriteLine("Hello, World!");
没有 class,没有 Main,代码就能跑。初学者容易以为 C# 变成了脚本语言——并没有。Roslyn 在编译时把顶级语句整体塞进一个自动生成的 Program 类的 Main 方法里,产出的程序集和你手写完整结构的版本字节级等价。证据是:一个程序集中只允许一个文件使用顶级语句,而且它天然成为入口点,因为入口必须是 Main。
这体现了理解 C# 的一条捷径:很多语法糖只是编译器替你做的改写。看到任何新语法,先问"它脱糖后是什么",糖衣下的结构往往几十年未变。后面遇到的自动属性、using 声明、await,全是同一个道理。
一个规范的控制台程序长这样:
using System; using System.Collections.Generic; namespace OrderSystem { public class Order { private readonly List<string> _items = new(); public int Id { get; init; } public void AddItem(string sku) => _items.Add(sku); } }
最外层是命名空间。它不是运行时概念——编译后命名空间只是类型全名里的点号分隔前缀,OrderSystem.Order 而已,不存在一个叫命名空间的运行时对象。它的价值全在编译期:组织类型名的层次、配合 using 指令缩短引用。文件顶部的 using System 让你可以写 Console 而不是 System.Console;C# 10 之后还可以对整个工程启用全局 using,常见系统命名空间一次声明全项目生效。
中间层是类型声明。class 是引用类型的声明方式,此外还有 struct(值类型)、interface、record 等,第二章逐一展开。这里注意 new() 目标类型表达式:左边字段已经声明为 List<string>,右边就不必重复,编译器从上下文推断。
最内层是成员:字段 _items、属性 Id、方法 AddItem。readonly 修饰字段表示只能在声明处或构造函数中赋值;init 表示属性只能在对象初始化时赋值,之后不可变——比 set 更安全的暴露方式。
方法体由语句构成。声明语句、选择语句、循环语句、跳转语句、异常处理语句几大类,多数语法与其他 C 系语言接近,这里只挑有 C# 特色的说。
模式匹配是近十年最值得掌握的语句级特性:
static string Classify(object shape) => shape switch { int n when n < 0 => "负数", int n => $"整数 {n}", string s => $"文本 {s}", null => "空值", _ => "未知" };
switch 表达式按模式从上到下测试,when 附加条件,_ 兜底。编译器会检查穷尽性——如果去掉 _ 且类型是 object 时虽不强制报错,但对枚举等封闭类型,漏分支会直接 CS8510 报错。这种"编译器替你查漏"的能力,是 C# 类型系统持续演进的方向。
另一个高频糖是 using 声明。需要确定性释放的资源(文件句柄、数据库连接):
using var fs = File.OpenRead("data.bin"); using var reader = new BinaryReader(fs);
块结束时按逆序自动调用 Dispose,等价于老写法的 try/finally 包裹。注意这依赖 IDisposable 接口,第二章讲 dispose 模式时展开。
C# 编译器对命名几乎无强制(除了接口以 I 开头这类少数惯例被工具链检查),但社区规范高度一致,遵守它能让你代码"看起来像 C#":
| 元素 | 规范 | 示例 |
|---|---|---|
| 类、方法、属性 | PascalCase | OrderService、CalculateTotal |
| 局部变量、字段 | camelCase,私有字段加 _ 前缀 |
totalCount、_cache |
| 接口 | I 加 PascalCase | IRepository |
| 常量 | PascalCase | MaxRetryCount |
⚠️ 常见坑:从 Java 过来的开发者习惯 camelCase 的方法名,写出
getTotal()这种 Java 风格方法名。C# 里它能编译能运行,但会在代码评审时被一眼认出是"外来口音"。
不用顶级语句时,入口是静态 Main,返回 void 或 int,可接收 string[] args。args 不含程序名,只含调用参数,这与 C 语言的 argv 不同。Main 可以是异步的(static async Task Main()),编译器会生成正确的启动代码等待它完成——这背后是运行时对异步入口的特殊处理,第四章讲状态机时你会重新理解这一行。
还有一个易被忽略的细节:顶级语句里 return; 直接结束程序,返回整数则用 Environment.Exit 或换成显式 Main 写法。理解这些边界情况的唯一办法还是那句话——想编译器把它改写成了什么。
Main,每程序集仅一个文件可用;下一节进入本章核心:值类型与引用类型在内存里的真实布局,那是理解一切性能问题的地基。