知识在于积累。
前言
好久没写博客了,因为在看《CLR via C#》的时候,竟然卡在了分部方法这一小节几天没下去。今天重新认真阅读,有些感悟,所以在此记录。
然后。
每天早晨第一句,《CLR via C#》真有意思。
分部类
我们用 partial 关键字告诉C#编译器:类、结构或接口的定义源代码可能要分散到一个或多个源代码文件中。
将类型源代码分散到多个文件的原因有三个:
- 源代码控制
假定类型定义包含大量的源代码,一个程序员把它从源代码控制系统中签出(check out)以进行修改。没有其他程序员能同事修改这个类型,除非之后执行合并。
使用 partial 关键字可将类型的代码分散到多个源代码文件中,每个文件都可以单独签出,多个程序员能同时编辑类型。
- 在同一个文件中将类或结构分解成不同的逻辑单元
可以创建一个类型来提供多个功能,让类型能提供完整的解决方案。为了简化实现,有时会在一个源代码文件中重复声明同一个分部类型。然后分部类型的每个部分都实现一个功能,并配以它的全部字段、属性、方法、事件等。这样就可方便的看到组合以提供一个功能完整的全体成员,从而简化代码。
与此同时,可以方便的将分部类型的一部分注释掉,以便从类中删除一个完整的功能,代之以另一个实现(通过分部类型的一个新的部分)。
- 代码拆分
在 Microsoft Visual Studio 中创建新项目时,一些源代码文件会作为项目一部分自动创建。这些源代码包含模板,能为项目开个好头。使用 Visual Studio 在设计图面上拖放控件时,Visual Studio 自动生成源代码,并将代码拆分到不同的源文件中。
这就提高的开发效率。
要将 paritial 关键字应用于所有文件中的类型。
这些文件编译到一起时,编译器会合并代码,在最后 .exe 或 .dll 程序集文件(或 .netmodule 模块文件)中生成单个类型。
“分类类型”功能完全由C#编译器实现,CLR对此一无所知,这解释了一个类型的所有源代码文件为什么必须使用相同的编程语言,而且必须作为一个编译单元编译到一起。
分部方法
假如我们现在在写一个工具,里面包含着C#源代码文件。我们写的这个工具有个功能是,需要在代码的某些位置定制类型的行为。
一般我们这么做,调用虚方法来进行定制。那么这个工具生成的代码必须包含虚方法的定义。另外这些方法的实现是什么事情都不做,直接返回了事。
现在如果想定制类的行为,就必须从基类派生并定义自己的类,重写虚方法来实现自己想要的行为。
看下面代码:
好。刚刚我们说的这个方法有两个问题。
- 类型必须是非密封的。不能用于值类型(值类型隐式密封)、不能用于静态方法(静态方法不能重写)。
- 效率问题。定义一个类型只是为了重写一个方法,这会浪费少量系统资源。另外,即使不想重写某个虚方法的行为,基类代码仍然需要调用一个什么都不做、直接就返回的虚方法。
我们现在利用分部方法就可以解决上面的问题。
修改一下刚刚的代码:
现在我们这个代码:
- 类型可以密封,也可以是静态类、值类型。
- 工具和开发者所生成的代码真的是一个类型定义的两个部分。
- 工具生成的代码包含分部方法的声明,要用 partial 关键字标记,没有主体。
- 开发者生成的代码实现这个声明,该方法也需要用 partial 关键字标记,有主体。
编译完成之后与虚方法实现的代码效果相同。
分部方法还有一个巨大的提升就是,不会生成多余的IL指令,使运行效率提升。
如果不想修改工具生成的类型的行为,那么根本不需要提供自己的源代码文件,如果没有实现分部方法,编译器就不会生成任何代表分部方法的元数据,也就是说编译器不会生成任何调用分部方法的IL指令。
更少的元数据和更少的IL指令,运行时的性能就能得到提升。
另外一些规则和原则
- 分部方法只能在分部类或结构中声明。
- 分部方法的返回类型始终是 void,任何参数都不能用 out 符号标记。
- 分部方法的声明和实现必须具有完全一致的签名。
- 如果没有对应的实现部分,便不能再代码中创建一个委托来引用这个分部方法。
- 分部方法总是被视为 private 方法,但是C#编译器禁止在分部方法声明之前添加 private 关键字。