在本章及本部分后续的章节,我将解释如何在一个类型中定义不同种类的成员,从而设计出符合自己需要的类型。
6.1类型的各种成员
在一个类型中可以定义0个或多个以下种类的成员:
- 常量:常量就是指出数据值恒定不变的一个符号。这些符号通常用于使代码更容易阅读和维护。常量通常与类型关联,不与类型的实例关联。从逻辑上讲,常量始终是静态成员。
- 字段:字段表示一个只读或可读可写的数据值。字段可以是静态的,这种情况下,字段被认为是类型状态的一部分。字段也可以是实例(非静态),这种情况下,字段被认为是对象状态的一部分。强烈建议将字段声明为私有的,防止类型或对象的状态被该类型外部的代码破坏。
- 实例构造器:将对象的实例字段初始化为良好初始状态的一种特殊方法。
- 类型构造器:将类型的静态字段初始化为良好初始状态的一种特殊方法。
- 方法:方法是一种特殊的函数,作用是更改或查询一个类型或对象的状态。作用于类型时,称为静态方法;作用于对象时,称为实例方法。方法一般会对类型或对象的字段执行读写操作。
- 操作符重载: 操作符重载实际是一种方法,它定义了将一个特定的操作符作用于对象时,应该如何操作这个对象。
- 转换符重载:转换操作符是定义如何隐式或显示的将对象从一种类型转型为另一种类型的方法。
- 属性:领用属性(property),可以使用一种简单的、字段风格的语法来设置或查询类型或对象的部分逻辑状态,同时保证状态不被遭到破坏。作用于类型的称为静态属性,作用于对象的称为实例属性。属性可以是没有参数的,也可以是有多个参数的。
- 事件:利用静态事件,一个类型可以向一个或多个静态或实例方法发送通知。而利用实例事件,一个对象可以向一个或多个静态或实例方法发送通知。提供事件的类型或对象的状态发生改变,通常就会引发事件。事件包含两种方法,允许静态或实例方法登记或注销对该事件的关注。
- 类型:类型可定义嵌套于其中的其他类型。通常用这个办法将一个大的、复杂的类型分解成更小的构建单元(building block),以简化实现。
无论使用什么编程语言,它的编译器都必须能处理你的源代码,为上述列表中的每一种成员生成元数据和IL代码。无论使用的编程语言是什么,元数据的格式都是完全一致的。正是因为这个特点,才使CLR成为公共语言运行时。
CLR使用公共元数据格式决定常量、字段、构造器、方法、属性和事件在运行时的行为。元数据是整个.Net Framework开发平台的关键,它实现了编程语言、类型和对象的无缝集成。
以下C#代码展示了一个类型定义,其中包含了所有可能的成员。
6.2类型的可见性
在文件范围内定义类型时,可以将类型的可见性指定为public和internal。
public类型不仅对它的定义程序集中的所有代码可见,还对其他程序集中的代码可见。
internal类型仅对定义程序集中的所有代码可见,对其他程序集中的代码不可见。
定义类型时,如果不显式指定类型的可见性,C#编译器默认将类型的可见性设为internal(两者中较有限的那一个)。
友元程序集
友元程序集功能用于访问内部成员;私有类型和私有成员仍然不可访问。
若要使程序集(程序集 B)能够访问另一个程序集(程序集 A)的内部类型和成员,应使用程序集 A 中的 InternalsVisibleToAttribute 属性
6.3成员的可访问性accessibility
在代码中引用一个成员时,成员的可访问性指出这种引用是否合法。
- private:访问仅限于包含该成员的类型或任何嵌套类型中的方法访问。
- protected:访问仅限于当前类和其子类。
- internal:访问仅限于所属程序集。
- internal protected:访问仅限于当前程序集或其子类(子类可以不属于当前程序集)。
- public:访问不受限制。
当然,任何成员想要被别人访问到,都必须在一个可见的类型内定义。例如,如果程序集AssemblyA定义了一个internal类型,该类型有一个public方法,那么程序集AssemblyB中的代码不能调用AssemblyA中的public方法,因为internal类型对于AssemblyB是不可见的。
在C#中,如果没有显式声明成员的可访问性,编译器通常默认选择private(限制最大的那个)。CLR要求接口类型的所有成员都具有public可访问性。
6.4静态类
静态类是不能实例化的,例如Console,Math,Environment和ThreadPool类。这些类只有static成员。我们直接使用它的属性与方法,静态类最大的特点就是共享,作用是将一组相关的成员组合到一起。例如Math类中定义了一组执行数学运算的方法。Math 类:为三角函数、对数函数和其他通用数学函数提供常数和静态方法。
static关键字只能应用于类,不能应用于结构(值类型)。这是因为CLR总是允许值类型实例化。
静态类的主要特点如下:
- 它们仅包含静态成员。
- 它们不能被实例化。
- 它们是密封的。
- 它们不能包含实例构造函数。
C#编译器对静态类进行了如下限制:
- 静态类必须直接从基类System.Object派生,从其他任何基类派生没有任何意义。继承只适用于对象,而你不能创建静态类的实例
- 静态类不能实现任何接口,这是因为只有使用类的一个实例时,才可以调用类的接口方法
- 静态类只能定义静态成员(字段、方法、属性和事件),任何实例成员都将导致编译器报错
- 静态类不能作为字段、方法参数或局部变量使用,因为它们都代表引用了一个实例的变量,这是不允许的
在类或结构内部定义的类型称为嵌套类型。例如:
class Container { class Nested { Nested() { } } }
通过static关键字修饰,是属于类,实例成员属于对象,在这个类第一次加载的时候,这个类下面的所有静态成员会被加载。
实例构造函数:使用 new 表达式创建某个类的对象时,会使用实例构造函数创建和初始化所有实例成员变量。
只要创建基于 CoOrds 类的对象,就会调用此实例构造函数。 诸如此类不带参数的构造函数称为“默认构造函数”。 然而,提供其他构造函数通常十分有用。 例如,可以向 CoOrds 类添加构造函数,以便可以为数据成员指定初始值:
class CoOrds { public int x, y; // constructor public CoOrds() { x = 0; y = 0; } public CoOrds(int x, int y) { this.x = x; this.y = y; } } class MainClass { static void Main() { CoOrds p1 = new CoOrds(); CoOrds p2 = new CoOrds(5, 3); } }
6.5 分部类、结构和接口
partial这个关键字告诉C#编译器,一个类、结构或者接口的定义源代码可能要分散到一个或多个源代码文件中。
当使用大项目或自动生成的代码(如由 Windows 窗体设计器提供的代码)时,将一个类、结构或接口类型拆分到多个文件中的做法就很有用。
局部类型适用于以下情况:
- 类型特别大,不宜放在一个文件中实现。
- 一个类型中的一部分代码为自动化工具生成的代码,不宜与我们自己编写的代码混合在一起。
- 需要多人合作编写一个类。
局部类型的注意点:
- 关键字partial是一个上下文关键字,只有和 class、struct、interface 放在一起时才有关键字的含义。因此partial的引入不会影响现有代码中名称为partial的变量。
- 局部类型的各个部分一般是分开放在几个不同的.cs文件中,但C#编译器允许我们将他们放在同一文件中。
6.6组件、多态和版本控制
组件软件编程(Component Software Programming)
下面列举了组件的一些特点:
- 组件(.Net中称为程序集)有已经发布的意思
- 组件有自己的标识(名称、版本、语言文化和公钥)
- 组件永远维持自己的标识(程序集中代码永远不会静态链接到另一个程序集中,.Net总是使用动态链接)
- 组件清楚指明它所依赖的组件(引用元数据表)
- 组件要文档化它的类和成员,C#语言通过源代码的XML文档和编译器的/doc命名行开关提供这个功能
- 组件必须指定它要求的安全权限,CLR的代码访问安全性(Code Access Security)机制提供了这个功能
- 组件要发布一个在任何“维护版本”中都不会改变的接口
在.Net中,版本号为1.2.3.4的程序集,其主版本号1,次版本号2,内部版本号3,修订号为4。
6.6.1 CLR如何调用虚方法、属性和事件
方法代表在类型或者类型的实例执行某些操作的代码。
在类型上执行操作称为静态方法。在类型的实例上执行操作称为非静态方法。
任何方法都有一个名称、一个签名和一个返回值(可以是void)。