欢迎光临散文网 会员登陆 & 注册

第 80 讲:C# 2 之分部类型

2022-01-17 10:38 作者:SunnieShine  | 我要投稿

C# 2 诞生了一种新的语法机制,它让我们的数据类型的书写和使用都变得更为方便。

Part 1 分部类型的概念

考虑一种情况。如果我们在书写代码的时候必须把代码放在同一个类型里面,如果这个类型非常庞大,处理起来相当麻烦的话,那么这个类型经常就会超过几千行甚至上万行代码。如果这样的代码列在一起,就非常不方便我们自己书写代码的时候去查找 API 的使用,以及 API 的执行过程。因此,C# 2 里带来了一种新的机制:分部类型(Partial Type)。

1-1 基本语法

我们可以允许一个类型声明分为多个文件来存储,每一个文件的类型声明都使用同一种数据类型的名字。比如说,假设我有一个数据类型叫做 StringHandler,那么我可以这么去写代码:

我们定义其中一个文件为主类型文件(Main File),它存储和标识这个数据类型的修饰符,以及一些比较关键的成员。接着,我们可以将一些文件分开独立存储,里面可以实现一些分开的内容,比如嵌套类,等等。每一个需要分离成不同文件的类型的声明上都带上 partial 这个新的关键字,挨着类型修饰符 struct 出现。

这里所说的主类型文件只是逻辑存在的一种说法,假设我这个类型是 public sealed class 修饰的,那么你完全可以将 publicsealed 修饰到不同的分部类型的声明上去:

编译器将自动合并所有的修饰符,即最终的修饰符是 public sealed class A,编译器知道去合并修饰符。不过,这种情况下我们就无法说清楚哪一个文件是主要的了。

不过,我们不建议你这么写代码。我们建议你在书写和使用的时候,尽量把修饰符放在同一个文件里去,将其作为主类型文件。

1-2 一般用途

一般来说,这种用法一般实现和使用在嵌套类型里比较多一些。比如刚才的 StringHandler 类型,假设它用来拼接长字符串的话,它应该就必须具有支持 foreach 循环的迭代器类型。假设这个迭代器类型是我们自己实现的:

这里,内层的嵌套类型 Enumerator 我们可以分离出文件来。

通过这样的机制,我们就可以拆分为不同的文件存储了,美观、大方。注意我们分离的是 StringHandler 类型,因此 partial 用在 StringHandler 上,而不是嵌套类型 Enumerator 上面。

Part 2 常见分部类型里需要注意的问题

2-1 partial 修饰符的位置

另外,目前只有 delegateenum 无法使用分部类型机制,而 classstructinterface 都可以使用 partial 修饰符来指示类型可以多文件分开存储。至于为什么委托和枚举不行……因为委托声明只需要一句话;而枚举类型里没有任何需求去分离不同的委托字段信息。partial 关键字一定要放在类型修饰符 classstructinterface 的前面。目前来说,partial 关键字的位置只能在类型修饰符的左边紧挨着,也就是说:

  • partial public sealed class A

  • public partial sealed class A

这些都是不对的写法。因为位置不正确。

2-2 重复使用相同的修饰符

C# 允许我们在不同的文件上使用相同的修饰符。也就是说,你多次在不同的分部类型文件上使用同一个修饰符,C# 是允许的。

2-3 分部类型的嵌套

C# 甚至支持分部类型嵌套。换句话说,你可以将嵌套类型分文件存储。不过,当前类型,以及它所处类型,以及所处类型的所处类型(可能还有更多的嵌套级别),全部标记上 partial 即可:

如果有这样的情况的话,你需要同时对 ABC 类型分文件存储的时候都标记 partial

请注意文件分离出来后的样子和修饰符的使用情况。

2-4 分部类型的类型继承和接口实现

是的,分部类型和普通的数据类型没有什么区别,只是分离开了罢了。分部类型也能实现接口和从基类型派生的规则。

没有什么区别。C# 也确实允许你随意去完成类型继承机制和接口实现机制,而且你甚至也能多次从同一个基类型派生,以及多次实现同一个接口。编译器它知道你已经完成了对接口的实现,以及对基类型的派生过程,因此它不会强求你必须只写一次的。

2-5 为什么编译器会允许我们多次重复书写相同的东西?

这个,其实是因为编译器生成代码的时候方便灵活处理。编译器有些时候为了方便,就会直接把头部全部读取下来,然后抄一遍。C# 这样的允许就保证了这种机制是可以允许和接受的;试想一下,假设 C# 不允许这种机制,即要求修饰符只能出现一次、不得重复书写,而且里面的接口实现、基类型派生的这些语句全部只能写一次的话,是不是就有点不灵活?

比如说,我有一个类型:

倘若编译器要生成别的跟 A 绑定起来的有关代码的话,因为 C# 允许我们重复相同的内容,因此,它生成的类型的头部直接抄写一遍也不会出现错误:

2-6 partial 修饰过的类型仍可以不分部

为了语法的通用性和灵活性,C# 允许对不分部的数据类型使用 partial 关键字。也就是说,如果这个类型没有分离到不同的文件的时候,这个类型仍然可以使用 partial 关键字。在这种情况下,partial 有没有都没关系,不影响编译器执行、分析和生成代码。

不过,要注意一点的是,这样修饰的话会带来一些隐患,这一点我们稍后会给出说明。

Part 3 泛型类型的分部

3-1 泛型类型的分部规则

泛型类型比正常的类型声明多了一个泛型参数部分,以及泛型参数约束部分(如果泛型参数需要约束的话)。C# 对泛型参数约束的要求还是和前面都差不多,但是!

因为泛型参数个数不同的同名类型是构成类型重载性的,所以泛型参数必须要在分部为多文件的时候,都全部写出来,不过,泛型约束可以不重复再写一遍;写一遍也行,但必须泛型约束是一样的,不能篡改和变动泛型约束。

比如下面给出的这个 A<T> 类型。下面给出了所有它的分部类型。其中注释掉的部分是错误的写法,原因也都写在后面了。

如果看不懂英语的话,我再解释一下吧。

  • partial class A<T>:可以,允许不写泛型约束;

  • partial class A<T> where T : struct:不可以,因为泛型约束不一样;

  • partial class A:可以,但由于它和 A<T> 类型构成类型重载,所以这个 A 完全就是一个新的数据类型;

  • partial class A<T> where T : class:可以;

  • partial class A<T1>:不可以,因为泛型参数名字不一样,数据类型 A<T>A<T1> 也不构成重载;

  • partial class A<T, U>:可以,但它和 A<T> 类型构成类型重载,所以 A<T, U> 也是一个全新的数据类型。

因此,请一定要注意问题。

3-2 为什么不建议在任何类型上都加上 partial 关键字?

另外,前文说到“留下隐患”指的就是这里的类型重载性。一个数据类型是不需要任何修饰符的,它将会默认为 internal 修饰的类型。而你使用 partial 关键字但没有注意类型的重载性的话,就完全可能使得这个类型完全和你原来的类型关联不上,导致暗藏的程序问题和书写代码的问题,所以,我们并不建议用户在自己实现类型的时候,随时都加上 partial 关键字;相反,我们只建议你必须分离文件的时候才使用该修饰符。


第 80 讲:C# 2 之分部类型的评论 (共 条)

分享到微博请遵守国家法律