1.2 程序结构与基本语法:编译单元的视角


1.2 程序结构与基本语法:编译单元的视角

本节摘要: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(值类型)、interfacerecord 等,第二章逐一展开。这里注意 new() 目标类型表达式:左边字段已经声明为 List<string>,右边就不必重复,编译器从上下文推断。

最内层是成员:字段 _items、属性 Id、方法 AddItemreadonly 修饰字段表示只能在声明处或构造函数中赋值;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 OrderServiceCalculateTotal
局部变量、字段 camelCase,私有字段加 _ 前缀 totalCount_cache
接口 I 加 PascalCase IRepository
常量 PascalCase MaxRetryCount

⚠️ 常见坑:从 Java 过来的开发者习惯 camelCase 的方法名,写出 getTotal() 这种 Java 风格方法名。C# 里它能编译能运行,但会在代码评审时被一眼认出是"外来口音"。

入口方法与参数

不用顶级语句时,入口是静态 Main,返回 voidint,可接收 string[] argsargs 不含程序名,只含调用参数,这与 C 语言的 argv 不同。Main 可以是异步的(static async Task Main()),编译器会生成正确的启动代码等待它完成——这背后是运行时对异步入口的特殊处理,第四章讲状态机时你会重新理解这一行。

还有一个易被忽略的细节:顶级语句里 return; 直接结束程序,返回整数则用 Environment.Exit 或换成显式 Main 写法。理解这些边界情况的唯一办法还是那句话——想编译器把它改写成了什么。

本节要点回顾

  • 顶级语句是糖:编译进自动生成的 Main,每程序集仅一个文件可用;
  • 命名空间是编译期概念:运行时只认类型全名;
  • 读 C# 的钥匙是"脱糖"思维:自动属性、using 声明、switch 表达式都是可还原的改写;
  • 模式匹配带穷尽性检查:优先用 switch 表达式替代长串 if-else;
  • 命名规范是社区契约:PascalCase 与 camelCase 的分工要形成肌肉记忆。

下一节进入本章核心:值类型与引用类型在内存里的真实布局,那是理解一切性能问题的地基。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U