第 85 讲:C# 2 之访问器上的访问修饰符
今天我们继续来看语法新拓展。
Part 1 引例
尽管目前 C# 的封装机制做得已经很优秀了,但仍然有极少数情况下,目前的语法做不到。
假设我有一个形状的抽象类型 Shape
,然后派生出 Circle
圆形、Rectangle
矩形等等形状的类型。
假设我有一个属性 Area
表示面积,用来计算面积数值。从另外一个层面,我们可以通过构造器初始化 Area
来达到赋值的过程,就不用运行时使用属性的时候才来计算了:
可以看到,这样的实现机制可以更加灵活地使用属性:我们为 Area
属性的 setter 设置了 protected
访问修饰符后,这个方法就只能用在当前类和它的派生类型里使用赋值操作了。
在原来,我们如果这么书写代码,就非常不合理:因为 setter 是 public
的,所以你完全可以随便篡改数值。我们拥有了限制 setter 的访问修饰符,就可以多样化使用属性达到更深层次的封装效果了。
Part 2 语法规则和细节
可以从前文的例子里看出,getter 和 setter 不必只通过属性的访问修饰符单纯作限制,而这个语法可以允许我们直接在 get
和 set
关键字上访问修饰符。那么,这个语法有什么细节上的规则和限制呢?
2-1 不能同时使用两次访问修饰符到两个访问器上
试想一下这样书写的代码是什么意思:
访问器
即直接把这个访问修饰符放在属性上去。这不是一样的吗?
2-2 访问器设置的访问修饰符必须小于属性本身的访问修饰符
这个说法有点不好理解,举个例子你就懂了。
假设我给属性设置的是 protected
访问修饰符,那么它的 getter 或者 setter 的访问修饰符上就不能使用 public
这种修饰符。因为 public
意味着任何时候都可以使用,而 protected
仅用于派生类型上。
假设这个语法合理的话:
从两个角度来说,public get
都没有意义:
第一,public
比 protected
范围还大,那么这么限制显然就没有意义,因为属性本身就限制它的可访问级别是只能派生类型和自己类型里可以看到。那么你设置 public
最多也就只能在这个范围里面随便使用这个 getter。说白了,getter 的访问级别是受到属性这个语法本身的制约的。可问题是这么做不就跟 protected
一样吗?那么写不写就无所谓了,因此 public get
没有意义;
第二,public
public get
也没有意义。
总之,设置了一个超过属性访问级别的访问修饰符到访问器上,是没有意义的。因此,C# 并不允许你这么做。
2-3 访问器只有一个的时候,不能这样加访问修饰符
这个比较好理解。如果一个属性单纯只有 getter 或者 setter 的时候,那么你往 getter 或 setter 上加访问修饰符的逻辑,就完全和在属性上直接修改访问修饰符是等价的。所以,你这种时候去追加访问修饰符没有任何意义,因此 C# 也不允许我们这么做。
比如这样的语法,就是不合适的。
Part 3 带不同修饰符的访问器的属性的继承和接口实现机制
属性的语法到现在算是有了一个比较多样化的升级,那么它能否用于继承和派生呢?答案是可以的。不过这里就需要注意一点细节了。下面我们来说说,跟继承派生相关的这种语法的用法和细节。
3-1 不能在提供重写的虚属性和抽象属性里出现 private
访问器
考虑一种情况。假设我有这样两个类型 C
和 D
:
先不考虑 setter 怎么实现代码,只关注于 setter 的访问修饰符的话,你觉得,这个 setter 的 private
修饰符合理吗?可能你觉得,好像还蛮合理的,因为你重新派生和实现是在派生类型里,而抽象属性上的 setter 有 private
,也只是逻辑上的一个限制。
实际上,不是这样。属性的任务是给字段提供赋值和取值的模式的。那么,属性就只有三种状态:
只有 getter:这种属性多用于模拟和代替表示一个无参方法,比如
Shape
类型给一个Area
方法来表示套公式以计算面积;只有 setter:这种属性多用于规划和隐式触发事件(字段只有赋值操作,而 setter 可以封装赋值和触发事件的逻辑);
getter 和 setter 都有:这种属性就更不用我多说了,到处都有用。
而如果出现 private
修饰符的访问器,就显得非常没有意义。private get
修饰的话,这种一般只考虑出现在类似刚才 Area
这种,只有 getter 的属性。那么这种情况和刚才说过的“只有一个访问器的属性”是一样的,因此它还不如将其定义为 private
的属性;而 private set
的话,它用作给对象的字段赋值。而本身字段的级别就比较低,低到什么程度呢?低到要么 private
要么 protected
,internal
和 protected internal
也有但很少。你想想,我在派生类型上重写属性的 setter 的时候,如果 private
修饰符允许的话,那么为什么我不直接给这个字段赋值呢?字段本身就已经低到内部才能看到,而我却绕了一个步骤用属性去赋值,结果属性的 setter 说不定访问级别比直接给字段赋值还要低(比如我原本字段是在 C
类型的,是 protected
修饰的,我有一个 D
类型从 C
类型派生,结果属性的赋值器却用 private
给这个字段赋值)。你说,这有没有起到封装的目的和作用?显然没有嘛。
所以,在重写属性(可能基类型是抽象属性,或者虚属性)的时候,都不允许出现 private
修饰的访问器。
3-2 不可变更从基类型拿下来的待重写属性的访问器的访问修饰符
这句话有点绕。举个例子就明白了。
我有这么一个属性,它是带有两个索引器需要我们重写,一个是 getter,一个是 protected
修饰过的 setter。这种属性在重写的时候,我们不能去更改 setter 的 protected
访问修饰符。因为你可能会把访问修饰符的范围改小或改大。改得更小则破坏了面向对象的设计规则,再次派生的时候你都看不到它了;而改得更大了则暴露出了本来应该封装不让外部看到的 setter,也破坏了面向对象的设计原则。所以,我们不能篡改修饰在访问器上的访问修饰符。
Part 4 它的用途
可以从前面给的例子里看出,这种机制似乎还不能和无法说服我使用这个语法机制来处理一些字段的数据。这里我们来说一下,封装的灵活性。
我们仍然假设 C
和 D
类型,并规定 _field
字段和 Field
属性。
_field