首页 / C# 入门教程 / partial 分部类与分部方法

C# 入门教程

partial 分部类与分部方法

本教程共 100 篇 · 第 38 篇 · 更新于 2026-07-31 · 约 11 分钟阅读

C#C# 入门教程partial分部类源生成器

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/SayHiAge/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 是纯编译期拼接:合并后的类型和「写在一起」完全等价,运行时零开销,也不会多出任何类型或方法。

最佳实践汇总

  1. 只对「生成代码 + 手写代码」或确实过大的类拆分,类型不大却硬拆成几片反而难读。
  2. 两半声明必须完全一致:命名空间、类名、类型(class/struct/interface/record)都要对上,否则编译器当它们是不同类,合并失败。
  3. 跨程序集不行partial 只能在同一程序集、同一编译单元内合并,不能把一个类一半放项目 A、一半放项目 B。
  4. 别去手动改生成的那一半:那是工具的地盘,你改了下次生成会被覆盖。
  5. 有返回值或 out 的分部方法必须实现;老式 private void 分部方法若无实现,调用处会被编译器悄悄抹成空操作。
Warning

别为了「看着文件短」而拆 partial。拆分带来认知成本:读代码的人得在多个文件间跳。只有当另一半确实是工具生成、或这片确实是天然独立的一大块时,才值得。

小结

partial 把同一类型拆成多段、编译期合并,零运行时成本,且不仅用于类,结构/接口/记录也支持,还能与嵌套类型叠加。它的最大价值是隔离「自动生成代码」与「手写代码」,也是源生成器的基础——你写分部声明,工具补全实现。分部方法用 partial 声明签名、另一处提供实现;C# 9 起它可带返回值、访问修饰符和真实方法体,有返回值就必须实现。C# 14 又把 partial 扩展到了事件和构造函数。