partial 用于解决代码生成与手写代码的冲突,而非组织业务逻辑;其核心约束包括同命名空间、同可访问性、语义统一及分部方法零开销特性。

partial 不是用来“组织业务逻辑”的,它解决的是代码生成与人工编写之间的冲突。如果你试图用它来拆分一个本该重构的巨型类,反而会埋下维护隐患。
partial class 必须满足的编译约束
编译器不会帮你检查命名空间或程序集是否一致,但一旦出错,报错信息往往指向最终合并后的类型,而不是你正在编辑的那个文件。
- 所有
partial class声明必须在同一个命名空间下,否则编译失败(错误:Type 'X' already defines a member called 'Y' with the same parameter types) - 所有部分必须使用相同的可访问性修饰符,比如一个写
public partial class A,另一个写internal partial class A,直接编译不通过 - 如果任意一部分加了
abstract或sealed,整个类型就具备该语义——哪怕其他部分没写,也不能反向覆盖 - 基类只能在一个部分中指定,但接口可以分散声明:
partial class A : Base和partial class A : IFirst, ISecond是合法的,最终类型同时继承Base并实现全部接口
partial void 方法的签名和实现必须严格匹配
分部方法不是普通重载,它没有运行时存在感:没实现就等于没定义,编译器直接删掉调用点。这种“零开销抽象”只适用于钩子场景,不能用于常规逻辑分支。
- 声明侧和实现侧的参数类型、顺序、名称必须完全一致;哪怕只是把
string s改成string value,编译器就认为这是两个不同方法,实现会被忽略 - 返回类型强制为
void,不能加async、不能有out或ref参数(C# 12 仍不支持) - 不能加任何访问修饰符(
private都不允许写),也不能加特性([Obsolete]会报错) - 调用未实现的
partial void不会报错,但也不会执行任何逻辑——容易漏掉关键回调,建议配合#pragma warning disable 8770显式压制警告并加注释说明意图
partial 类型在 Source Generators 中的实际协作方式
现代 C# 项目里,partial 最常出现在 Source Generator 输出的代码中,比如 EF Core 的 [GenerateEntity] 或自定义属性驱动的代码生成。这时候你写的 .Logic.cs 文件不是“补充”,而是“契约的一方”。
- Generator 输出的文件通常带
partial+internal+ 特定命名后缀(如User.g.cs),你绝不能手动修改它;你的User.cs必须用相同名字、相同命名空间、相同可访问性去配对 - 如果 Generator 生成了
partial void OnSaving(),而你在自己的文件里写了实现,那它会在 Save 流程中被注入;但如果 Generator 升级后删掉了这个方法,你的实现代码会变成死逻辑,且无编译错误 - VS 的“转到定义”默认跳转到 Generator 生成的文件,想快速定位自己写的部分,得右键 → “查找所有引用”,再过滤掉
.g.cs
最容易被忽略的点是:partial 不提供任何运行时反射可见性保障。typeof(A).GetMethods() 返回的是合并后的完整方法列表,但你无法知道某个 void DoX() 是来自哪个物理文件——这对调试、文档生成、甚至某些 AOP 框架会造成隐性影响。


















