第六章 类型和成员基础

目录:

6.1 类型的各种成员

6.2 类型的可见性

6.3 成员的可见性

6.4 静态类

6.5 分部类,结构和接口

6.6 组件,多态和版本控制

6.1 类型的各种成员

常量:数据值恒定不变的符号。常亮总与类型管理,不与类型的实例关联。逻辑上总是静态成员

字段:只读或可读/可写的数据值。

实例构造器:将对象的实例字段初始化为良好初始状态的特使方法。

类型构造器:将类型的静态字段初始化为良好初始状态的特殊方法。

方法:更改或查询类型或对象状态的函数。作用于类型称为静态方法,作用于对象称为实例方法。

操作符重载:实际是方法,定义了当操作符作用于对象时,应该如何操作该对象。

转换操作符:定义如何隐士或显示将对象从一种类型转型为另一种类型的方法。

属性:允许用简单的,字段风格的语法设置或查询类型或对象的逻辑状态,同时保证状态不被破坏。

事件:静态事件允许类型向一个或多个静态或实例方法发送通知。实例事件允许对象向一个或多个静态或实例方法发送通知。引发事件通常是为了响应提供事件的类型或对象的状态的改变。事件包含两个方法,允许静态或实例方法登记或注销对该事件的关注。还有一个委托字段来维护已登记的方法集。

类型:类型可定义其他嵌套类型。

6.2 类型的可见性

public:不仅对定义程序集中的代码可见,还对其他程序集中的代码可见。

internal:仅对定义程序集中的代码可见。

友元程序集:能够使定义为internal的类型给另一个程序集的代码访问。 生程序集时,可用”InternalsVisibleTo“特性:该特性获取友元程序集名称和公钥的字符串参数。

6.3 成员的可见性

CLR术语 C#术语 描述
Private private 成员只能由定义类型或任何嵌套类型中方法访问
Family protected 成员只能由定义类型,任何嵌套类型或者不管在任何程序集中的派生类型中的方法访问
Family and Assembly (不支持) 成员只能由定义类型,任何嵌套类型或者同一程序集中定义的任何派生类型中的方法访问
Assembly internal 成员只能由定义程序集中的方法访问。
Family or Assembly protected internal 成员可由任何嵌套类型,任何派生类型或者定义程序集中的任何方法访问
Public public 成员可由任何程序集的任何方法访问

在C#中,如果没有显式声明成员的可访问性,编译器通常默认选择private。CLR要求接口类型的所有成员都具有Public可访问性。编译器禁止开发人员显式指定接口成员的可访问性,它会自动将所有成员的可访问性设为public。

派生类重写基类型定义的成员时,C#编译器要求原始成员和重写成员具有相同的可访问性。

6.4 静态类

静态类的作用是组合一组相关的成员。在C#中,要用static关键字定义不可实例化的类。该类只能应用于类,不能应用于结构。因为CLR总是允许值类型实例化,这是没办法阻止的。

限制:

静态类必须直接从基类System.Object派生,从其他任何基类派生都没有意义。

静态类不能实现任何接口,这是因为只有使用类的实例时,才可调用类的接口方法。

静态类只能定义静态成员(字段,方法,属性和事件),任何实例成员都会导致编译器报错。

静态成员不能作为字段,方法参数或局部变量使用,因为它们都代表引用了实例的变量,而这是不允许的。

6.5 分部类,结构和接口

partial关键字告诉编译器:类,结构或接口的定义源代码可能要分散到一个或多个源代码文件中。

将类型源代码分散到多个文件的原因有三:

源代码控制

在同一个文件中或结构分解成不同的逻辑单元

代码拆分

6.6 组件,多态和版本控制

组件软件编程(Component SoftWare Programming,CSP)是OOP发展到极致的成果。下面列举组件的一些特点。

组件(.Net Framework称为程序集)有“已经发布”的意思。

组件有自己的标志(名称,版本,语言文化和公钥)。

组件永远维持自己的标志(程序集中的代码永远不会静态链接到另一个程序集中;.NET总是使用动态链接)

组件清楚指明它所依赖的组件(引用元数据表)。

组件应编挡它的类和成员。C#语言通过源代码内的XML文档和编译器的/doc命令行开关提供这个共功能。

组件必须指定它需要的安全权限。CLR的代码访问安全性(Code Access Security,CAS)机提供这个功能。

组件要发布在任何“维护版本”中都不会改变的接口(对象模型)。“维护版本”代表组件的新版本,它向后兼容组件的原始版本。

.NET Framework 中的版本包含四个部分:主版本号(major version),次版本号(minor version),内部版本号(build number)和修订号(revision)。major/minor部分通常代码程序集的一个连续的,稳定的功能集,而build/revision部分通常代表对这个功能集的一次维护。

将一个组件(程序集)中定义的类型作为另一个组件(程序集)中的一个类型的基类型使用时,便会发生版本控制问题。显然,如果基类的版本(被修改得)低于派生类,派生类的行为也会改变,这可能造成类的行为失常。 

与组件版本控制相关的C#关键字

C#关键字 类型 方法/属性/事件 常量/字段
abstract 表示不能构造该类型的实例 表示为了构造派生类型的实例,派生类型必须重写并实现这个成员 (不允许)
virtual (不允许) 表示这个成员可由派生类型重写 (不允许)
override (不允许) 表示派生类型正在重写基类型的成员 (不允许)
sealed 表示该类型不能用作基类型 表示这个成员不能被派生类型重写,只能将该关键字应用于重写虚方法的方法 (不允许)
new 应用于嵌套类型,方法,属性,常量或字段时,表示该成员与基类中相似的成员无任何关系

 

6.6.1 CLR如何调用虚方法,属性和事件

编译代码,编译器会在程序集的方法定义表中写人记录项,每个记录项都用一组标志(flag)指明方法是实例方法,虚方法还是静态方法。

写代码调用方法,生成调用代码的编译器会检查方法定义的标志,判断应如何生成IL代码来正确调用方法。CLR提供两个方法调用指令。

call:该IL指令可调用静态方法,实例方法和虚方法。用call指令调用静态方法,必须指定方法的定义类型。用call指令调用实例方法或虚方法,必须指定引用了对象的变量。call指令假定该变量不为null。call指令经常用于以非虚方式调用虚方法。

callvirt:该IL指令可调用实例方法和虚方法,不能调用静态方法。用callvirt指令调用实例方法或虚方法,必须指定引用了对象的变量。用callvirt指令调用非虚实例方法,变量的类型指明了方法的定义类型。用callvirt指令调用虚实例方法,CLR调查发出调用的对象的实际类型,然后以多态方式调用方法。为了确定类型,发出调用的变量绝不能是null。换言之,编译这个调用时,JIT编译器会生成代码来验证变量的值是不是null。如果是,callvirt指令造成CLR抛出NullReferenceException异常。

设计类型时应尽量减少虚方法数量。首先,调用虚方法的速度比调用非虚方法慢。其次,JIT编译器不能内嵌虚方法,这进一步影响性能。第三,虚方法使组件版本控制变得更脆弱。第四,定义基类型时,经常要提供一组重载的简便方法。如果希望这些方法时多态的,最好的方法是使最复杂的方法成为虚方法,使所有重载的简便方法成为非虚方法。

6.6.2 合理使用类型的可见性和成员的可访问

密封类相对于非密封类的优势:

版本控制

性能

安全性和可预测性

定义类时遵循的原则:

定义类时,除非确定要将其作为基类,并允许派生类对它进行特化,否则总是显式地指定为sealed类。

类的内部,将数据字段定义为private。

在类的内部,将自己的方法,属性和事件定义为priavate和非虚。如果要公开就定义为public,尽量避免定义为protected或internal,因为这会使类型面临更大的安全风险。最后才考虑使用virtual,因为虚成员会放弃许多控制,丧失独立性,变得彻底依赖于派生类型的正确行为。

OOP有一条古老的格言,大意时当事情变得过于复杂时,就搞更多的类型出来。当算法的实现开始变得复杂时,我会定义一些辅助类型来封装独立的功能。如果定义的辅助类型只由一个“超类型”使用,我会在“超类型”中嵌套这些辅助类型。这样除了可以限制范围,还允许嵌套的辅助类型中的代码引用“超类型”中定义的私有成员。

6.6.3 对类型进行版本控制时的虚方法的处理

使用override重写基类虚方法。

每天学习一丢丢
原文地址:https://www.cnblogs.com/terry-1/p/9824615.html