目 录CONTENT

文章目录

CSharp(二十八)分部类(Partial Class)详解

CSharp(二十八)分部类(Partial Class)详解

1. 什么是分部类

一句话解释

分部类(Partial Class) 允许你把一个类的定义拆分多个文件中书写。编译器在编译时会把所有部分自动合并成一个完整的类。

生活类比

想象你在写一本很厚的小说:

方式 类比 说明
普通类 整本书写在一个大本子上 一个文件搞定
分部类(Partial) 把书分成上、中、下三册 多个文件,最终还是一本书

关键是:读者(编译器)看完上中下三册后,得到的是一个完整的故事,而不是三个独立的故事。

图示理解

编译前(源代码)                      编译后(IL代码)
┌─────────────────┐
│  Person1.cs     │ ──┐
│  partial class  │   │
│  {              │   │    合并     ┌─────────────────┐
│    字段和属性    │   │  ───────→  │   class Person  │
│  }              │   │            │   {              │
├─────────────────┤   │            │     所有字段      │
│  Person2.cs     │   │            │     所有属性      │
│  partial class  │ ──┤            │     所有方法      │
│  {              │   │            │     构造函数      │
│    方法         │   │            │   }              │
│  }              │   │            └─────────────────┘
├─────────────────┤   │
│  Person3.cs     │   │
│  partial class  │ ──┘
│  {              │
│    构造函数      │
│  }              │
└─────────────────┘

2. 为什么需要分部类

问题场景

假如你要写一个"学生管理系统"的 Student 类,它需要包含:

  • 10 个字段和属性(学号、姓名、性别、年龄、地址、电话...)
  • 15 个方法(注册、选课、考试、毕业...)
  • 5 个构造函数
  • 3 个事件

如果全部写在一个文件里,这个文件可能轻松超过 500 行甚至上千行,阅读和维护都非常困难。

分部类的价值

问题 不用分部类 用分部类
文件太大 一个文件几百上千行,难找代码 按功能拆分到多个小文件,每个文件几十行
多人协作 多人修改同一文件,容易冲突 每人负责不同的文件,互不干扰
自动生成代码 手写代码和生成代码混在一起 生成代码放一个文件,手写代码放另一个文件
代码组织 不同功能的代码混在一起 字段一个文件、方法一个文件、逻辑分组清晰

3. 分部类的基本语法

核心关键字:partial

只需要在 class 关键字前面加一个 partial,就声明了一个分部类。

// 语法格式
// 访问修饰符 partial class 类名
// {
//     类成员
// }

最简单的例子

假设我们要定义一个 Calculator(计算器)类,把它拆成两个文件:

文件1:Calculator.Basic.cs —— 基础运算

public partial class Calculator
{
    // 字段
    private double _result;

    // 加法
    public double Add(double a, double b)
    {
        _result = a + b;
        return _result;
    }

    // 减法
    public double Subtract(double a, double b)
    {
        _result = a - b;
        return _result;
    }
}

文件2:Calculator.Advanced.cs —— 高级运算

public partial class Calculator
{
    // 乘法
    public double Multiply(double a, double b)
    {
        _result = a * b;
        return _result;
    }

    // 除法
    public double Divide(double a, double b)
    {
        if (b == 0)
        {
            throw new DivideByZeroException("除数不能为零!");
        }
        _result = a / b;
        return _result;
    }

    // 获取当前结果
    public double GetResult()
    {
        return _result;
    }
}

使用这个类(和普通类完全一样)

class Program
{
    static void Main(string[] args)
    {
        Calculator calc = new Calculator();

        Console.WriteLine($"10 + 5 = {calc.Add(10, 5)}");        // 输出:10 + 5 = 15
        Console.WriteLine($"10 - 3 = {calc.Subtract(10, 3)}");   // 输出:10 - 3 = 7
        Console.WriteLine($"6 * 4 = {calc.Multiply(6, 4)}");     // 输出:6 * 4 = 24
        Console.WriteLine($"20 / 4 = {calc.Divide(20, 4)}");     // 输出:20 / 4 = 5

        Console.WriteLine($"当前结果:{calc.GetResult()}");       // 输出:当前结果:5
    }
}

注意:使用 Calculator 时,你完全感觉不到它是分部类。Add 方法虽然在文件1里定义的,Divide 方法虽然在文件2里定义的,但它们都属于同一个对象。你可以像调用普通类一样调用所有方法。


4. 代码是如何合并的

合并过程详解

当编译器编译你的项目时,它会这样处理分部类:

  1. 收集:找到所有带 partial class 类名 的文件
  2. 合并:把所有部分的成员(字段、属性、方法等)合并到一起
  3. 编译:把合并后的完整类编译成 IL(中间语言)代码

动手验证

你可以通过下面的完整例子来感受合并的效果:

文件1:Person.Fields.cs

public partial class Person
{
    // 基本信息字段
    public string Name;
    public int Age;
    public string Address;
}

文件2:Person.Methods.cs

// 注意:这里需要 using System; 因为用了 Console
using System;

public partial class Person
{
    // 自我介绍方法 —— 可以直接使用文件1中定义的 Name、Age
    public void Introduce()
    {
        Console.WriteLine($"大家好,我叫{Name},今年{Age}岁,住在{Address}");
    }

    // 判断是否成年
    public bool IsAdult()
    {
        return Age >= 18;
    }
}

文件3:Person.Constructor.cs

public partial class Person
{
    // 无参构造函数
    public Person()
    {
        Name = "未知";
        Age = 0;
        Address = "未知";
    }

    // 带参构造函数
    public Person(string name, int age, string address)
    {
        Name = name;
        Age = age;
        Address = address;
    }
}

测试代码

using System;

class Program
{
    static void Main(string[] args)
    {
        // 使用带参构造函数(定义在文件3中)
        Person p1 = new Person("张三", 25, "北京市朝阳区");
        p1.Introduce();  // 输出:大家好,我叫张三,今年25岁,住在北京市朝阳区
        Console.WriteLine($"是否成年:{p1.IsAdult()}");  // 输出:是否成年:True

        // 使用无参构造函数(定义在文件3中)
        Person p2 = new Person();
        p2.Name = "李四";
        p2.Age = 16;
        p2.Address = "上海市浦东新区";
        p2.Introduce();  // 输出:大家好,我叫李四,今年16岁,住在上海市浦东新区
        Console.WriteLine($"是否成年:{p2.IsAdult()}");  // 输出:是否成年:False
    }
}

关键理解:虽然三个文件中都写了 public partial class Person,但它们不是三个类,而是同一个类的三个部分。因此,方法 Introduce() 可以直接访问字段 Name(即使它们写在不同的文件中)。


5. 分部类的核心规则

使用分部类时必须遵守以下规则:

规则1:所有部分都必须加 partial 关键字

// ✅ 正确:两个文件都用了 partial
// 文件A
public partial class MyClass { }
// 文件B
public partial class MyClass { }

// ❌ 错误:文件B 忘了写 partial
// 文件A
public partial class MyClass { }
// 文件B
public class MyClass { }  // 编译错误!

规则2:访问修饰符必须一致

// ✅ 正确:都是 public
// 文件A
public partial class MyClass { }
// 文件B
public partial class MyClass { }

// ❌ 错误:访问修饰符不一致
// 文件A
public partial class MyClass { }
// 文件B
internal partial class MyClass { }  // 编译错误!

注意:如果所有部分都不写访问修饰符,则默认都是 internal,这也算一致。

规则3:不能把同一个成员声明两次

// ❌ 错误:Name 字段在两个文件中都声明了
// 文件A
public partial class Person
{
    public string Name;  // 第一次声明
}

// 文件B
public partial class Person
{
    public string Name;  // 编译错误!重复声明
}

规则4:基类只需在一个部分中指定

// ✅ 正确:只在文件A中指定继承关系
// 文件A
public partial class Dog : Animal { }

// 文件B
public partial class Dog { }  // 不需要再写 : Animal

// ❌ 错误:两个文件指定了不同的基类
// 文件A
public partial class Dog : Animal { }
// 文件B
public partial class Dog : Creature { }  // 编译错误!

规则5:所有部分必须在同一个命名空间和同一个程序集中

// ✅ 正确:都在同一个命名空间中
namespace MyApp.Models
{
    public partial class Student { }
}

namespace MyApp.Models  // 同样的命名空间
{
    public partial class Student { }
}

// ❌ 错误:不同命名空间
namespace MyApp.Models
{
    public partial class Student { }
}

namespace MyApp.Services  // 不同的命名空间
{
    public partial class Student { }  // 编译错误!这是另一个类了
}

规则速查表

项目 要求
partial 关键字 每个部分都必须有
访问修饰符 所有部分必须一致
类名 完全相同(包括大小写)
命名空间 相同
程序集 同一个程序集(不能跨 DLL 拆分)
基类 只在一个部分声明即可,多了会冲突
成员声明 同一个成员不能出现在两个部分中
接口实现 可以在不同部分中实现同一个接口的不同成员

6. 分部方法(Partial Method)

什么是分部方法

分部方法是分部类中的一个特殊函数。它允许你在一个部分中声明方法签名,在另一个部分中可选地实现它。

为什么需要分部方法

分部方法最主要的使用场景是:代码生成工具生成的钩子方法

比如 Visual Studio 自动生成的 WinForms 代码中有很多类似 OnLoad()OnClick() 这样的钩子,允许你在另一个文件中"挂接"自定义逻辑。如果你不需要,这些方法就完全不存在(编译时会被移除,零性能开销)。

基本语法

// 文件A(通常是自动生成的代码)
public partial class Order
{
    // 声明分部方法(只有签名,没有方法体)
    partial void OnOrderCreated();

    public void CreateOrder()
    {
        Console.WriteLine("订单创建中...");

        // 调用分部方法 —— 如果没实现,这行代码编译时会被移除
        OnOrderCreated();

        Console.WriteLine("订单创建完成!");
    }
}

// 文件B(手写代码 —— 选择性实现)
public partial class Order
{
    // 实现分部方法(可选!不实现也完全没问题)
    partial void OnOrderCreated()
    {
        Console.WriteLine(">>> 日志:新订单已创建,时间:" + DateTime.Now);
    }
}

测试

Order order = new Order();
order.CreateOrder();
// 输出:
// 订单创建中...
// >>> 日志:新订单已创建,时间:2026-06-26 10:30:00
// 订单创建完成!

分部方法的规则

规则 说明
返回值 必须是 void(不能返回值)
访问修饰符 不能写(隐式为 private
参数 可以有参数,但不能有 out 参数
是否必须实现 不必须。如果不实现,编译时声明和调用都会被完全移除
声明与实现 声明和实现都必须在分部类的不同部分
关键字 声明和实现都用 partial 关键字

带参数的分部方法

// 声明(文件A)
public partial class Logger
{
    partial void Log(string message, int level);

    public void DoSomething()
    {
        // 调用
        Log("开始执行任务", 1);

        // ... 执行任务 ...

        Log("任务执行完毕", 1);
    }
}

// 实现(文件B)
public partial class Logger
{
    partial void Log(string message, int level)
    {
        string levelText = level switch
        {
            1 => "信息",
            2 => "警告",
            3 => "错误",
            _ => "未知"
        };
        Console.WriteLine($"[{levelText}] {DateTime.Now:HH:mm:ss} - {message}");
    }
}

分部方法的核心价值

代码生成的文件                         你手写的文件
┌─────────────────────────┐          ┌─────────────────────────┐
│ partial class MyForm    │          │ partial class MyForm    │
│ {                       │          │ {                       │
│   partial void OnLoad();│  钩子    │   partial void OnLoad() │
│                         │ ======>  │   {                     │
│   void Initialize()     │          │     // 你的自定义逻辑    │
│   {                     │          │   }                     │
│     // 自动生成的代码    │          │ }                       │
│     OnLoad();  ←──调用   │          └─────────────────────────┘
│   }                     │
│ }                       │
└─────────────────────────┘

如果不实现 OnLoad:调用 OnLoad() 那行代码编译时直接被删除,零开销!
如果实现了 OnLoad:就会像普通方法调用一样执行。

一句话总结:分部方法 = 代码生成工具给你留的"可选的填空位置",填了就执行,不填就当不存在。


7. 实战场景一:WinForms / WPF 自动生成代码

这是分部类最常见、最重要的应用场景。

问题

当你用 Visual Studio 拖拽一个按钮到 WinForms 窗口上时,VS 会自动生成大量代码(布局、样式、事件绑定等)。如果这些代码和你手写的业务逻辑混在同一个文件中:

  • 你修改业务代码时可能不小心破坏自动生成的代码
  • 自动生成代码更新时可能覆盖你的手写代码
  • 文件变得非常臃肿

解决方案:分部类

VS 将代码自动分成两个文件:

你的项目目录
├── Form1.Designer.cs    ← 自动生成的(不要手动修改!)
└── Form1.cs             ← 你手写的业务逻辑

Form1.Designer.cs(自动生成,不要去改它):

partial class Form1
{
    // 自动生成的控件声明
    private System.Windows.Forms.Button btnSubmit;
    private System.Windows.Forms.TextBox txtName;
    private System.Windows.Forms.Label lblResult;

    // 自动生成的初始化代码
    private void InitializeComponent()
    {
        this.btnSubmit = new System.Windows.Forms.Button();
        this.txtName = new System.Windows.Forms.TextBox();
        this.lblResult = new System.Windows.Forms.Label();

        // 位置、大小、样式设置...
        this.btnSubmit.Text = "提交";
        this.btnSubmit.Click += new System.EventHandler(this.btnSubmit_Click);
    }
}

Form1.cs(你手写业务逻辑的地方):

using System;
using System.Windows.Forms;

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();  // 调用自动生成的方法
    }

    // 按钮点击事件 —— 你的业务逻辑
    private void btnSubmit_Click(object sender, EventArgs e)
    {
        string name = txtName.Text;
        if (string.IsNullOrWhiteSpace(name))
        {
            lblResult.Text = "请输入姓名!";
        }
        else
        {
            lblResult.Text = $"你好,{name}!欢迎使用本系统。";
        }
    }
}

关键要点

┌──────────────────────────────────────────────────────────────┐
│                      重要!请记住:                            │
│                                                              │
│  Designer.cs 文件 = 机器生成的 UI 骨架                         │
│  .cs 文件 = 你写的业务逻辑                                     │
│                                                              │
│  永远不要手动修改 Designer.cs!                                │
│  它会在你拖拽控件时自动更新。                                   │
└──────────────────────────────────────────────────────────────┘

8. 实战场景二:团队协作开发

问题

假设一个 OrderService(订单服务)类有 2000 行代码,包含:

  • 订单创建逻辑(张三负责)
  • 订单查询逻辑(李四负责)
  • 订单支付逻辑(王五负责)

如果三个人都在同一个文件中写代码,Git 合并时必然产生冲突

解决方案:按功能拆分成多个分部文件

文件:OrderService.Create.cs —— 张三负责

public partial class OrderService
{
    // 创建订单
    public Order CreateOrder(string customerName, List<OrderItem> items)
    {
        var order = new Order
        {
            Id = Guid.NewGuid(),
            CustomerName = customerName,
            Items = items,
            Status = OrderStatus.Pending,
            CreatedAt = DateTime.Now
        };

        // 计算总价
        order.TotalAmount = items.Sum(i => i.Price * i.Quantity);

        // 保存到数据库
        _orderRepository.Save(order);

        return order;
    }
}

文件:OrderService.Query.cs —— 李四负责

public partial class OrderService
{
    // 根据ID查询订单
    public Order GetById(Guid orderId)
    {
        return _orderRepository.FindById(orderId);
    }

    // 查询用户的全部订单
    public List<Order> GetByCustomer(string customerName)
    {
        return _orderRepository.FindByCustomer(customerName);
    }

    // 分页查询
    public List<Order> GetPaged(int page, int pageSize)
    {
        return _orderRepository.FindPaged(page, pageSize);
    }
}

文件:OrderService.Payment.cs —— 王五负责

public partial class OrderService
{
    // 处理支付
    public PaymentResult ProcessPayment(Guid orderId, PaymentInfo paymentInfo)
    {
        var order = _orderRepository.FindById(orderId);
        if (order == null)
        {
            return new PaymentResult { Success = false, Message = "订单不存在" };
        }

        if (order.Status != OrderStatus.Pending)
        {
            return new PaymentResult { Success = false, Message = "订单状态不正确" };
        }

        // 调用支付网关
        bool paymentSuccess = _paymentGateway.Charge(order.TotalAmount, paymentInfo);

        if (paymentSuccess)
        {
            order.Status = OrderStatus.Paid;
            order.PaidAt = DateTime.Now;
            _orderRepository.Update(order);
            return new PaymentResult { Success = true, Message = "支付成功" };
        }
        else
        {
            return new PaymentResult { Success = false, Message = "支付失败,请重试" };
        }
    }
}

文件:OrderService.Common.cs —— 公共部分

public partial class OrderService
{
    // 依赖注入的仓储和支付网关
    private readonly IOrderRepository _orderRepository;
    private readonly IPaymentGateway _paymentGateway;

    // 构造函数(也只需要在一处定义)
    public OrderService(IOrderRepository orderRepository, IPaymentGateway paymentGateway)
    {
        _orderRepository = orderRepository;
        _paymentGateway = paymentGateway;
    }
}

团队协作的 Git 流程

张三改 OrderService.Create.cs   ──→  Git 提交 ✓(无冲突)
李四改 OrderService.Query.cs    ──→  Git 提交 ✓(无冲突)
王五改 OrderService.Payment.cs  ──→  Git 提交 ✓(无冲突)

三个人改三个不同的文件 → 合并时完全没有冲突!

9. 实战场景三:大型类的模块化管理

即使是一个人开发,当类变得很大时,用分部类按功能模块拆分会极大提升可读性。

不好的写法(一个文件,全部混在一起)

// ❌ 一个巨大的文件 —— 难以维护
public class StudentManager
{
    // 20 个字段
    // 15 个属性
    // 30 个方法
    // 10 个事件
    // 5 个构造函数
    // 总共上千行...

    // 你找一个方法要滚动很久...
}

好的写法(按功能拆分)

项目文件结构:
├── StudentManager.Core.cs       ← 核心字段、属性、构造函数
├── StudentManager.Enroll.cs     ← 选课相关方法
├── StudentManager.Grade.cs      ← 成绩相关方法
├── StudentManager.Report.cs     ← 报表统计方法
└── StudentManager.Event.cs      ← 事件定义和处理

StudentManager.Core.cs

public partial class StudentManager
{
    // ===== 核心数据和构造函数 =====

    private List<Student> _students = new List<Student>();
    private List<Course> _courses = new List<Course>();

    public string SchoolName { get; set; }
    public int TotalStudents => _students.Count;

    public StudentManager(string schoolName)
    {
        SchoolName = schoolName;
    }
}

StudentManager.Enroll.cs

public partial class StudentManager
{
    // ===== 选课相关方法 =====

    public bool EnrollStudent(Student student, Course course)
    {
        if (course.CurrentStudents >= course.MaxCapacity)
        {
            Console.WriteLine($"课程 {course.Name} 已满,选课失败!");
            return false;
        }

        student.Courses.Add(course);
        course.CurrentStudents++;
        Console.WriteLine($"{student.Name} 成功选修 {course.Name}");
        return true;
    }

    public void DropCourse(Student student, Course course)
    {
        if (student.Courses.Remove(course))
        {
            course.CurrentStudents--;
            Console.WriteLine($"{student.Name} 退选了 {course.Name}");
        }
    }
}

StudentManager.Grade.cs

public partial class StudentManager
{
    // ===== 成绩相关方法 =====

    public void SetGrade(Student student, Course course, double score)
    {
        if (!student.Courses.Contains(course))
        {
            Console.WriteLine($"{student.Name} 没有选修 {course.Name},不能设置成绩");
            return;
        }

        student.Grades[course.Id] = score;
        Console.WriteLine($"{student.Name} 的 {course.Name} 成绩设为:{score}");
    }

    public double GetAverageGrade(Student student)
    {
        if (student.Grades.Count == 0) return 0;
        return student.Grades.Values.Average();
    }
}

拆分前后对比

维度 拆分前 拆分后
单文件行数 1000+ 行 每个文件 50-100 行
查找方法 疯狂滚动/搜索 直接打开对应文件
与功能无关的干扰 高(其他功能的代码也在) 低(只看相关功能的代码)
改坏其他功能的概率 较高 较低

10. 综合示例

下面是一个完整的实战例子,模拟一个"图书管理系统"的 Book 类:

Book.Core.cs —— 核心属性

using System;

public partial class Book
{
    // ===== 基本信息属性 =====
    public string ISBN { get; set; }
    public string Title { get; set; }
    public string Author { get; set; }
    public decimal Price { get; set; }
    public DateTime PublishDate { get; set; }

    // ===== 库存信息 =====
    public int TotalCopies { get; set; }
    public int BorrowedCopies { get; set; }

    // ===== 计算属性 =====
    public int AvailableCopies => TotalCopies - BorrowedCopies;
    public bool IsAvailable => AvailableCopies > 0;

    // ===== 标签 =====
    public List<string> Tags { get; set; } = new List<string>();
}

Book.Borrow.cs —— 借阅/归还

using System;

public partial class Book
{
    // ===== 借阅 =====
    public bool Borrow(string borrowerName)
    {
        if (!IsAvailable)
        {
            Console.WriteLine($"《{Title}》已全部借出,暂时不可借阅。");
            return false;
        }

        BorrowedCopies++;
        Console.WriteLine($"《{Title}》被 {borrowerName} 借走。");
        Console.WriteLine($"  剩余可借:{AvailableCopies}/{TotalCopies}");
        return true;
    }

    // ===== 归还 =====
    public bool Return(string borrowerName)
    {
        if (BorrowedCopies <= 0)
        {
            Console.WriteLine($"《{Title}》没有借出记录,无法归还。");
            return false;
        }

        BorrowedCopies--;
        Console.WriteLine($"{borrowerName} 归还了《{Title}》。");
        Console.WriteLine($"  剩余可借:{AvailableCopies}/{TotalCopies}");
        return true;
    }
}

Book.Display.cs —— 显示信息

using System;

public partial class Book
{
    // ===== 显示详细信息 =====
    public void DisplayInfo()
    {
        Console.WriteLine("══════════════════════════════");
        Console.WriteLine($"  书名:{Title}");
        Console.WriteLine($"  作者:{Author}");
        Console.WriteLine($"  ISBN:{ISBN}");
        Console.WriteLine($"  价格:¥{Price:F2}");
        Console.WriteLine($"  出版日期:{PublishDate:yyyy-MM-dd}");
        Console.WriteLine($"  库存状态:{AvailableCopies}/{TotalCopies}(可借/总数)");
        Console.WriteLine($"  标签:{string.Join(", ", Tags)}");
        Console.WriteLine("══════════════════════════════");
    }

    // ===== 显示简要信息 =====
    public void DisplayBrief()
    {
        string status = IsAvailable ? "✓ 可借" : "✗ 已借完";
        Console.WriteLine($"《{Title}》- {Author} | ¥{Price} | {status}");
    }
}

Book.Constructor.cs —— 构造函数和工厂方法

using System;

public partial class Book
{
    // ===== 默认构造函数 =====
    public Book()
    {
        ISBN = "";
        Title = "未知";
        Author = "未知";
        Price = 0;
        PublishDate = DateTime.Now;
        TotalCopies = 1;
        BorrowedCopies = 0;
    }

    // ===== 完整构造函数 =====
    public Book(string isbn, string title, string author, decimal price,
                DateTime publishDate, int totalCopies)
    {
        ISBN = isbn;
        Title = title;
        Author = author;
        Price = price;
        PublishDate = publishDate;
        TotalCopies = totalCopies;
        BorrowedCopies = 0;
    }

    // ===== 工厂方法:快速创建一本书 =====
    public static Book CreateQuick(string title, string author, decimal price)
    {
        return new Book
        {
            ISBN = Guid.NewGuid().ToString("N").Substring(0, 13),
            Title = title,
            Author = author,
            Price = price,
            PublishDate = DateTime.Now,
            TotalCopies = 3
        };
    }
}

Program.cs —— 测试

using System;

class Program
{
    static void Main(string[] args)
    {
        // 方式1:工厂方法快速创建
        Book book1 = Book.CreateQuick("C#从入门到精通", "张三", 79.90m);
        book1.Tags.AddRange(new[] { "编程", "C#", "入门" });
        book1.DisplayBrief();

        Console.WriteLine();

        // 方式2:构造函数创建
        Book book2 = new Book(
            "978-7-111-12345-6",
            "设计模式之美",
            "李四",
            99.00m,
            new DateTime(2025, 1, 15),
            5
        );
        book2.Tags.AddRange(new[] { "设计模式", "进阶", "架构" });

        // 借阅和归还
        book2.DisplayInfo();
        book2.Borrow("王五");
        book2.Borrow("赵六");
        book2.Borrow("钱七");
        Console.WriteLine();
        book2.Return("王五");
        Console.WriteLine();

        // 最终状态
        book2.DisplayBrief();
    }
}

运行结果

《C#从入门到精通》- 张三 | ¥79.90 | ✓ 可借

══════════════════════════════
  书名:设计模式之美
  作者:李四
  ISBN:978-7-111-12345-6
  价格:¥99.00
  出版日期:2025-01-15
  库存状态:5/5(可借/总数)
  标签:设计模式, 进阶, 架构
══════════════════════════════
《设计模式之美》被 王五 借走。
  剩余可借:4/5
《设计模式之美》被 赵六 借走。
  剩余可借:3/5
《设计模式之美》被 钱七 借走。
  剩余可借:2/5

王五 归还了《设计模式之美》。
  剩余可借:3/5

《设计模式之美》- 李四 | ¥99.00 | ✓ 可借

11. 常见问题(FAQ)

Q1:分部类可以跨项目(程序集)吗?

不可以。 分部类必须在同一个程序集(同一个 .dll 或 .exe)中。如果你想跨程序集扩展一个类,请使用扩展方法(Extension Method)

Q2:分部类能继承吗?

可以。 分部类的继承规则和普通类完全一样:

// 文件A
public partial class Dog : Animal { }

// 文件B
public partial class Dog { }  // 不需要重复写 : Animal

子类也可以用分部类:

// 文件A
public partial class Puppy : Dog { }
// 文件B
public partial class Puppy { }

Q3:一个分部类最多能拆成多少个文件?

理论上没有限制。但实际开发中,超过 3-5 个文件就需要反思:是不是这个类承担的职责太多了?可能需要考虑重构,拆分成多个独立的类。

Q4:接口可以用 partial 吗?

可以。 接口也支持分部定义:

// 文件A
public partial interface IRepository
{
    void Add(object entity);
    void Delete(object entity);
}

// 文件B
public partial interface IRepository
{
    object FindById(int id);
    List<object> FindAll();
}

Q5:结构体(struct)可以用 partial 吗?

可以。 分部结构体和分部类的用法完全一样:

// 文件A
public partial struct Point
{
    public int X;
    public int Y;
}

// 文件B
public partial struct Point
{
    public double Distance => Math.Sqrt(X * X + Y * Y);
}

Q6:如果部分文件用了 abstractsealedstatic,每个文件都要写吗?

不需要,但必须一致。 只需要在其中一个部分中声明即可:

// ✅ 正确
// 文件A
public abstract partial class BaseService { }
// 文件B
public partial class BaseService { }  // 自动也是 abstract

// ❌ 错误:不一致
// 文件A
public abstract partial class BaseService { }
// 文件B
public sealed partial class BaseService { }  // 冲突!

Q7:分部类和继承有什么区别?

这是初学者最容易混淆的问题:

对比维度 分部类(Partial) 继承(Inheritance)
本质 同一个类的代码拆分 不同类之间的关系
关键字 partial : 基类名
成员访问 所有成员都是自己的 只能访问基类中允许的成员
类的数量 最终是一个 至少两个类(父类和子类)
内存中的对象 一个对象 子类是一个对象,其中包含基类部分
可以跨程序集吗 ❌ 不可以 ✅ 可以

简单区分口诀:分部类 ="一个人穿不同的衣服",继承 = "父子关系"。


12. 小结与练习

核心要点回顾

┌─────────────────────────────────────────────────────────────┐
│                      分部类核心概念总结                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. partial 关键字 → 把一个类拆分到多个文件                    │
│                                                             │
│  2. 编译器合并 → 最终仍然是一个完整的类                         │
│                                                             │
│  3. 所有部分 → 必须用 partial,访问修饰符必须一致               │
│                                                             │
│  4. 主要用途 → 自动生成代码分离 / 团队协作 / 大文件拆分          │
│                                                             │
│  5. 分部方法 → 可选实现的方法钩子,不实现则零开销                │
│                                                             │
│  6. 使用建议 → 一般 2-3 个部分足够,太多说明类设计有问题         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

动手练习

练习1(基础):创建一个 Temperature 分部类,拆成两个文件:

  • 文件1:字段 _celsius(double),属性 CelsiusFahrenheit(摄氏/华氏互转)
  • 文件2:方法 Display() 输出当前温度

练习2(进阶):创建一个 BankAccount(银行账户)分部类,拆成三个文件:

  • BankAccount.Core.cs:账号、户名、余额等字段和属性
  • BankAccount.Transaction.cs:存款 Deposit()、取款 Withdraw()、转账 Transfer()
  • BankAccount.Report.cs:打印交易记录 PrintStatement()

练习3(综合):模仿 WinForms 的场景:

  • 创建一个分部类 Game,模拟一个游戏类
  • Designer 文件(自动生成的):定义玩家血量、等级字段,以及一个分部方法 OnLevelUp()
  • 手写文件:实现 OnLevelUp(),当玩家升级时输出祝贺信息;实现 GainExperience() 方法,满经验时调用 OnLevelUp()

学习建议:分部类的关键在于理解"形式上是多个文件,实质上是一个类"。多在 WinForms/WPF 项目或团队协作中实践,自然就能体会到它的好处。

0
博主关闭了当前页面的评论