partial 分部类与分部方法
本教程共 100 篇 · 第 38 篇 · 更新于 2026-07-31 · 约 11 分钟阅读
38. partial 分部类与分部方法
本节目标:学完你能把一个类拆到多个文件,并看懂自动生成代码里的 partial。
有时一个类太大,或一部分由工具自动生成、一部分由你手写。C# 用 partial(分部)关键字允许把同一个类型拆成多段,编译时再拼成完整的一个。
分部类:同一类分到多文件
在每一段前面都写 partial class 类名,编译器会把它们合并。下面为了演示方便写在同一文件,真实项目里通常分在两个 .cs 文件。
namespace CSharpDemo;
partial class Person
{
public string Name { get; set; }
public void SayHi() => Console.WriteLine($"我是 {Name}");
}
partial class Person
{
public int Age { get; set; }
public void ShowAge() => Console.WriteLine($"我 {Age} 岁");
}
var p = new Person { Name = "小明", Age = 18 };
p.SayHi();
p.ShowAge();
合并后,Person 同时拥有 Name/SayHi 和 Age/ShowAge,像写在同一个文件里一样。
Note
partial只是编译期的「拼接提示」,不产生任何运行时开销,合并后的类和你写在一起完全等价。
它解决了什么痛点
分部类最常见的用处,是把机器生成的代码和人手写的代码隔开:
- WinForms / WPF 的设计器文件(如
Form1.Designer.cs)放自动布局代码; - 你自己的逻辑写在
Form1.cs里,两者都是同一个partial class Form1; - 工具重新生成设计器文件时,绝不会覆盖你手写的那一半。
现代 .NET 的**源生成器(source generator)**也依赖这个机制:它自动产出 partial 类/方法的实现,你只管写声明。
Tip新手看到
partial别慌,它往往意味着「另一半是工具写的」。你改自己那份,别去动生成的那份。
分部方法:可填可不填的钩子
**分部方法(partial method)**的签名写在一处、实现写在另一处,都用 partial 标记。调用方只管调用,有没有实现由另一半决定。
namespace CSharpDemo;
partial class Order
{
public void Place() => OnPlaced(); // 调用钩子
partial void OnPlaced(); // 声明,无方法体
}
partial class Order
{
partial void OnPlaced() // 提供实现
{
Console.WriteLine("订单已提交,发通知");
}
}
var o = new Order();
o.Place();
C# 9 之后的重要变化
早期的 partial 方法限制很死:只能是 private void、不能有实现、不能有 out 参数。从 C# 9 起放宽了——分部方法可以:
- 有访问修饰符(如
public); - 有返回值、有
out参数; - 在「实现那一半」真正写出方法体。
namespace CSharpDemo;
partial class Calculator
{
public partial int Double(int x); // 声明,带返回值
}
partial class Calculator
{
public partial int Double(int x) => x * 2; // 实现
}
var c = new Calculator();
Console.WriteLine(c.Double(5)); // 10
Note若分部方法有返回值或
out参数,则必须提供实现,否则编译报错。反之,若是老式的private void且无实现,编译器会把所有调用悄悄抹掉(变成空操作),不会报错。
C# 14 的延伸
C# 14 进一步允许 partial 事件和 partial 构造函数,思路一致:声明与实现分离,方便源生成器注入逻辑。本书基线是 C# 14,所以你在新项目里见到 partial 事件也属正常。
partial 不只用于类
除了 partial class,结构、接口、记录也都支持 partial。规则一样:多处声明、编译合并。典型如 partial struct 用来把大型值类型拆开维护。
namespace CSharpDemo;
partial struct Point
{
public int X;
}
partial struct Point
{
public int Y;
}
源生成器:partial 的真正舞台
现代 .NET 的**源生成器(source generator)**会在编译时扫描你的代码,自动生成 partial 类型的另一半实现。你只写声明,生成的代码补上实现,两边拼成完整类型。这就是为什么你在用某些库(如序列化、MVVM 框架)时,会看到大量 partial——另一半是工具写的。
Note你不用手写生成器也能受益:只要遵循约定写
partial声明,生成器就接管实现。这是partial在当代 .NET 里最重要的角色。
C# 9 前后的 partial 方法对比
| 项目 | C# 8 及以前 | C# 9 起 |
|---|---|---|
| 访问修饰符 | 只能 private | 任意 |
| 返回值 | 必须 void | 可有返回值 |
out 参数 | 不允许 | 允许 |
| 方法体 | 不能有 | 可以有 |
带返回值或 out 的分部方法必须提供实现;老式的 private void 若无实现,调用处被编译器安静地抹掉。
常见坑
- 两半对不上:命名空间或类名有一处写错,编译器就当它们是不同的类,合并失败或类型缺失。
- 跨程序集不行:
partial只在同一程序集、同一编译单元内合并,不能把一个类的一半放项目 A、一半放项目 B。 - 滥用拆分:类型不大却硬拆成几片,反而难读。只有「生成代码 + 手写代码」或确实过大的类才值得。
Tip判断要不要用 partial:问自己「另一半是不是工具生成的,或这片是不是天然独立的一大块」。都不是,就别拆。
partial 与嵌套类型组合
partial 和嵌套类型可以叠加:一个分部类里再嵌套另一个分部类型,拆分粒度更细。只要每段都标 partial、名字一致,编译器照常合并。
namespace CSharpDemo;
partial class Outer
{
partial class Inner
{
public int X;
}
}
partial class Outer
{
partial class Inner
{
public int Y;
}
}
分部方法的真实用武之地
源生成器常让开发人员写「分部方法的声明」,自己补「实现」。例如某些 MVVM 库里,你写 [ObservableProperty] private int age;,工具自动生成对应的 partial 属性实现。你只管声明字段,样板代码由生成器补全。
Note你无需自己写生成器就能享受这点:遵循库的约定写
partial声明,生成器接管实现。这是当代 .NET 里partial最关键的价值。
partial 类也能分散成员
不只是把类一分为二,还可以把字段放一半、方法放另一半,甚至不同人维护不同片段。只要都在同一命名空间、同一程序集,合并后就是完整类型。
namespace CSharpDemo;
partial class Note
{
public string Title { get; set; }
}
partial class Note
{
public void Print() => Console.WriteLine(Title);
}
new Note { Title = "备忘" }.Print(); // 备忘
Tip常见坑:两半的命名空间或类名有一处不一致,编译器就当它们是不同的类,合并失败。拆分时务必保证声明完全一致。
partial 的设计初衷
它解决的是「同一份类型,两个作者(人或工具)都要改」的矛盾。最典型的场景:WinForms/WPF 设计器自动生成一部分代码,你手写另一部分,两者都是同一个 partial class,工具重新生成时绝不会覆盖你那一半。到了现代 .NET,源生成器(source generator)把它发扬光大——编译期让工具扫代码、自动补全 partial 类型的另一半,你只写声明。没有 partial,机器生成和个人修改就只能挤在一个文件里,互相踩踏。
Note
partial是纯编译期拼接:合并后的类型和「写在一起」完全等价,运行时零开销,也不会多出任何类型或方法。
最佳实践汇总
- 只对「生成代码 + 手写代码」或确实过大的类拆分,类型不大却硬拆成几片反而难读。
- 两半声明必须完全一致:命名空间、类名、类型(
class/struct/interface/record)都要对上,否则编译器当它们是不同类,合并失败。 - 跨程序集不行:
partial只能在同一程序集、同一编译单元内合并,不能把一个类一半放项目 A、一半放项目 B。 - 别去手动改生成的那一半:那是工具的地盘,你改了下次生成会被覆盖。
- 有返回值或
out的分部方法必须实现;老式private void分部方法若无实现,调用处会被编译器悄悄抹成空操作。
Warning别为了「看着文件短」而拆
partial。拆分带来认知成本:读代码的人得在多个文件间跳。只有当另一半确实是工具生成、或这片确实是天然独立的一大块时,才值得。
小结
partial 把同一类型拆成多段、编译期合并,零运行时成本,且不仅用于类,结构/接口/记录也支持,还能与嵌套类型叠加。它的最大价值是隔离「自动生成代码」与「手写代码」,也是源生成器的基础——你写分部声明,工具补全实现。分部方法用 partial 声明签名、另一处提供实现;C# 9 起它可带返回值、访问修饰符和真实方法体,有返回值就必须实现。C# 14 又把 partial 扩展到了事件和构造函数。