23人参与 • 2026-03-05 • Asp.net
分部类(partial class).cs
这个特性不是用来解决业务逻辑混乱的,而是为了解决机器生成代码与人工编写代码之间的冲突。
partial class 放在 a 文件,你写一个 partial class 放在 b 文件。互不干扰,平安无事。
假设你有一个用户类,一分部由数据库工具生成,一分部是你自己写的验证逻辑。
文件 1: user.generated.cs (机器生成,不要动)
public partial class user
{
public int id { get; set; }
public string name { get; set; }
}
文件 2: user.logic.cs (你写的逻辑)
public partial class user
{
public bool validate()
{
return !string.isnullorempty(name);
}
}
在 c# 中,您可以使用 partial 关键字将类、结构、方法或接口的实现拆分到多个 .cs 文件中。编译器在编译程序时会将来自多个 .cs 文件的所有实现组合起来。
考虑以下包含 employee 类的 employeeprops.cs 和 employeemethods.cs 文件。
// employeeprops
public partial class employee
{
public int empid { get; set; }
public string name { get; set; }
}
// employeemethods
public partial class employee
{
//constructor
public employee(int id, string name){
this.empid = id;
this.name = name;
}
public void displayempinfo() {
console.writeline(this.empid + " " this.name);
}
}上面,employeeprops.cs 包含 employee 类的属性,而 employeemethods.cs 包含 employee 类的所有方法。这些文件将被编译成一个 employee 类。
示例:组合类
public class employee
{
public int empid { get; set; }
public string name { get; set; }
public employee(int id, string name){
this.empid = id;
this.name = name;
}
public void displayempinfo(){
console.writeline(this.empid + " " this.name );
}
}partial 修饰符只能紧接在 class、struct 或 interface 关键字之前。分部类还支持分部方法。这就像是一个“钩子(hook)”。
// 在 a 文件定义钩子
partial void ondatachanged();
// 在 b 文件实现钩子(如果不实现,编译器会直接删掉这个调用,零性能损耗)
partial void ondatachanged()
{
console.writeline("数据变了!");
}分部类或结构可以包含一个方法,该方法被拆分到分部类或结构的两个单独的 .cs 文件中。其中一个 .cs 文件必须包含该方法的签名,而另一个文件可以包含分部方法的可选实现。方法的声明和实现都必须具有 partial 关键字。
public partial class employee
{
public employee() {
generateempid();
}
public int empid { get; set; }
public string name { get; set; }
partial void generateemployeeid();
}
public partial class employee
{
partial void generateemployeeid()
{
this.empid = random();
}
}上面,employeeprops.cs 包含分部方法 generateemployeeid() 的声明,该方法在构造函数中使用。employeemethods.cs 包含 generateemployeeid() 方法的实现。以下代码演示了如何创建一个使用分部方法的 employee 类对象。
class program
{
static void main(string[] args)
{
var emp = new employee();
console.writeline(emp.empid); // prints genereted id
console.readline();
}
}partial 关键字,并且必须返回 void。in 或 ref 参数,但不能有 out 参数。简单粗暴地说,分部方法(partial method)的意义在于:给机器生成的代码“预留后悔药”,同时不给运行环境增加一丁点负担。
这种设计解决了软件工程中一个经典的矛盾:“自动化生成的代码”与“个性化业务需求”的冲突。
在大型项目中(尤其是使用 entity framework 或 wpf 时),工具会自动生成成千上万行代码。
partial void onsomething()。它告诉你:“我在这里留了个口子,你想写逻辑就去另一个文件写,不写也没关系。”这是它和 virtual 方法或 event(事件)最大的区别。
.dll 时会直接抹除这个调用。| 方案 | 机器生成代码中的动作 | 开发者动作 | 后果 |
|---|---|---|---|
| 普通方法 | 直接写死逻辑 | 无法干预 | 必须修改生成的文件,重构即地狱。 |
| 虚方法 (virtual) | 定义 virtual 方法 | override 重写 | 存在虚函数表查询开销,必须实例化对象。 |
| 分部方法 | 声明 partial 方法并调用 | 实现 partial 方法 | 不实现则代码消失,实现则无缝嵌入,两全其美。 |
只要是虚方法(virtual method),哪怕你大括号里一个字都不写,它在运行时依然有“身份支出”。
作为老司机,我给你拆解一下这背后的“隐形账单”。
虚方法的本质是运行时多态。编译器无法在编译时确定你到底要执行哪个方法,所以它得留个心眼。
vtable。vtable 里查到方法真正的内存地址,最后才跳过去执行。在 c# 中,虚表(vtable) 是一种底层机制,用于支持面向对象编程中的多态性和虚方法调用。它是一个数据结构,存储了类的虚方法的地址,以便在运行时动态调用正确的方法实现。
哪怕方法体是空的,这套“三级跳”的动作一次都少不了。
这是性能差距最大的地方。
| 特性 | 分部方法 (partial method) | 虚方法 (virtual method) |
|---|---|---|
| 未实现/空实现时 | 彻底消失。编译器直接把调用代码删了。 | 依然存在。cpu 照样执行跳转指令。 |
| 内存占用 | 0 额外占用。 | 每个对象多出指针空间(4/8 bytes)。 |
| 调用速度 | 极快(直接调用或内联)。 | 较慢(需要查表跳转)。 |
| 灵活性 | 编译时决定,不能跨程序集。 | 运行时决定,支持动态加载插件。 |
你可以根据这个逻辑来选:
partial 的情况:你写的是底层框架或代码生成工具,你想给开发者留个“可选的开关”,而且对性能有极致追求。如果不写逻辑,你希望这功能像从未存在过一样。virtual 的情况:你需要真正的多态。比如你写了一个 animal 类,你想让 dog 和 cat 在运行时表现出不同的 eat() 行为。答案很简单:你不需要知道,也不需要选。
因为在 c# 中,分部方法(partial method)遵循的是**“有且仅有一个实现”**的原则。
在你提供的代码中,编译器会报错,原因是你写了两个实现(方法体 {})。你可以把分部方法的机制理解为:
如果你在两个地方都写了 { ... }(哪怕一个是空的 {}),编译器就会像见到两个同名同姓的人一样陷入混乱,直接报错。
如果你希望在某些情况下输出 “bbb”,在另一些情况下“什么都不做”,你不能靠定义两个方法实现,而是要靠逻辑判断。
正确的写法应该是这样的:
// 【生成代码部分】
public partial class myclass
{
// 只有声明,没有大括号!这代表“此处预留钩子”
partial void onopen();
public void dowork()
{
onopen(); // 编译器在这里调用钩子
}
}
// 【开发者代码部分】
public partial class myclass
{
// 如果你想执行逻辑,就实现它
partial void onopen()
{
if (datetime.now.hour > 12)
{
console.writeline("bbb");
}
// 否则什么都不做(这就是你想要的“空方法”效果)
}
}分部方法的设计初衷不是为了让你做选择题,而是为了**“无缝注入”**。
.dll 里会把 onopen() 彻底抹除。调用它的地方(如 dowork 内部)也会被删掉。就像这段代码从未存在过。它不存在“调用哪一个”的问题,因为最终编译出来的类里,只有一个 onopen。
那这样执行的时候,不会冲突么,或者有两个分部类,他会执行哪一个
不会冲突,因为在编译器眼里,它们压根就不是两个类,而是一个类。
你可以把“分部类”想象成一块拼图:源代码阶段你手里拿着几块碎片,但编译完之后,这些碎片已经严丝合缝地粘在一起,变成了一块完整的板子。
partial 关键字的类。如果发现命名空间相同、类名相同的 partial 类,它会把它们所有的成员(字段、属性、方法、构造函数)全部收集起来,塞进同一个 il(中间语言)定义里。public int id,就不能在 b 文件里再定义一个 public int id,否则编译器会报错:“类已经包含该成员的定义”。既然物理上合二为一了,就不存在“执行哪一个”的问题。程序的入口(比如 main 方法)调用该类的方法时,该在哪就在哪。

如果两个分部类里都写了构造函数,情况如下:
public user()(无参构造),编译会报错。因为同一个类里不能有两个签名完全一样的构造函数。partial method)。虽然编译器帮你合体了,但在开发中要遵循以下守则:
public partial,b 文件写了 internal partial,编译器会直接罢工。访问级别必须完全统一。baseclassa,b 继承自 baseclassb,编译报错。一个类只能有一个亲爹。// 生成代码分部
public partial class myclass
{
public string name{get;set;}
partial void onopen()
{
console.writeline("aaa");
};
}
// 开发者代码分部
public partial class myclass
{
public string age{get;set;}
partial void onopen()
{
console.writeline("bbb");
};
}虽然 partial 类可以将代码拆分,但它依然遵循 c# 最基本的语法规则:同一个成员(方法)在同一个类中不能有多个实现。
分部方法(partial method)的设计初衷是:一个地方定义声明(类似占位符),另一个地方编写实现(可选)。
在你的例子中,两个分部类都写了具体的 { ... } 逻辑块,编译器会认为你对同一个方法定义了两次,直接抛出编译错误:
cs0757: 分部方法声明具有多个实现方法。
// ------------------------------------
// 文件 a:生成代码分部(通常由工具生成)
// ------------------------------------
public partial class myclass
{
public string name { get; set; }
// 这里只定义“钩子”的签名,不写大括号逻辑
partial void onopen();
public void trigger()
{
onopen(); // 编译器在这里埋下调用点
}
}
// ------------------------------------
// 文件 b:开发者代码分部(由你编写)
// ------------------------------------
public partial class myclass
{
public string age { get; set; }
// 这里编写具体的业务逻辑
partial void onopen()
{
console.writeline("bbb");
}
}执行结果: 调用 trigger() 方法时,屏幕会输出:bbb。
这是分部方法最聪明的地方。如果你在文件 b 中没有编写 partial void onopen() 的实现:
onopen() 的调用。partial 只是编译器层面的语法糖。运行时的 clr(公共语言运行时)根本不知道分部类的存在,它看到的永远是一个完整的类。partial 注入新功能。到此这篇关于c#中分部类和分部方法(partial)的文章就介绍到这了,更多相关c#分部类和分部方法内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论