.NET中的特殊类型成员
.NET中的特殊类型成员
.NET中的特殊类型成员
----微软 .NET平台系列文章之三
译文/赵湘宁
在前面的两篇文章中,我们研究了类型的基础知识。本文我们将考察类型能定义的某些特殊成员。在大力简化处理类型及其对象实例需要的语法方面,这些类型有助于面向对象设计。
类型构造器
你已经熟悉了什么是构造器,它负责对象实例状态的初始化。除了实例构造器以外,Microsoft(r).NET公共语言运行时(CLR)还支持类型构造器(也叫做静态构造器、类构造器或类型初始化)。类型构造器可被应用到接口,类和数值类型。它允许任何在类型中声明的成员被访问之前实现必要的初始化。类型构造器不需要参数并且总是返回void类型。类型构造器只访问类型的静态字段并且其通常的目的是初始化这些字段。在类型的任何实例被创建之前以及类型的任何静态字段或方法被引用之前,必须要保证已经运行了类型构造器。
许多语言(包括C#)在定义类型时都自动产生类型构造器。但是某些语言需要显式(手工)实现类型构造器。
为了理解类型构造器,让我们研究一下列在C#中定义的类型:
class Atype {
static int x = 5;
}
在建立这个代码时,编译器自动地为产生Atype类型构造器。这个构造器负责初始化静态字段x为值5。如果你使用ILDasm,很容易认出类型构造器方法,因为它们的名字都是.cctor(对于类构造器而言)。
在C#中,通过在类型中定义静态构造器方法,你可以自己实现类型构造器。关键字static的使用意味着这时类型构造器,而不是实例构造器。下面是一个非常简单的例子:
class AType {
static int x;
static AType() {
x = 5;
}
}
这个类型定义与前面的相同。注意类型构造器决不能试图创建自己的类型实例,而且构造器也不能引用类型的非静态成员。
最后,如果你用C#编译器编译下列代码,它产生单独的类型构造器方法:
class AType {
static int x = 5;
static AType() {
x = 10;
}
}
这个构造器首先初始化x=5,然后,初始化x=10。换句话说,编译器产生的结果类型构造器首先包含静态字段的初始化代码,随后是类型构造器的代码。
属性
许多类型定义的属性可以被重新获得或修改。这些属性常常都是用类型字段成员来实现的。例如,下面是包含有两个字段的类型定义:
class Employee {
public String Name;
public Int32 Age;
}
如果创建这个类型的实例,那么很容易用以下代码得到或设置属性:
Employee e = new Employee();
e.Name = "Jeffrey Richter"; // 设置名字属性
e.Age = 36; // 设置年龄属性
Console.WriteLine(e.Name); // 显示 "Jeffrey Richter"
用这种方式使用属性非常普通。但以我的观点看,上述代码不会向列出的那样被实现。面向对象设计和编程的立约之一便是数据抽象。它的意思就时类型字段不能用公共字段暴露出来,因为它太容易被修改,太容易让人写出不恰当地使用这个字段的代码,从而破坏对象的状态。例如,某人很容易编写下面的代码破坏Employee对象:
e.Age = -5; //人的年龄怎么会是-5呢?
所以说,在设计类型时,我强烈建议所有字段都是私有的(private)或至少是受保护的(protected)——决不要公共的(public)。然后,让使用类型的人能Get或Set属性,专门为此提供方法。打包对字段的访问的方法就叫做存取器(或访问器方法)方法。这些方法能随时实现完整性检查并保证对象的状态不被破坏。例如,我重写了前面定义过的Employee类,代码如图一。虽然这是一个简单的例子,但你能从中明白抽象数据字段的巨大好处,你还能从中明白如何轻松实现只读属性,或者仅仅通过不去实现某个存取器方法来轻松达到只写属性。
图一中显示的数据抽象方法有两个缺点。第一,因为要实现附加的函数,所以要多写一些代码。第二,类型的使用者现在必须要调用方法而不是仅仅引用单个的字段名:
e.SetAge(36); // Updates the age
e.SetAge(-5); // Throws an exception
我想,所有的人都会同意这些缺点与其优点比起来显得微不足道,但运行时仍然提供了一种属性机制,多少使得第一个缺点容易忍受了,并且完全消除了第二个缺点。
图二中的类使用了属性,其功能和图一所示的类相同。正如你所看到的,属性简化了一些代码,但更重要的是允许调用这项下面一样写自己的代码:
.Age = 36; // 更新年龄
e.Age = -5; // 掷出异常Throws an exception
Get属性存取器的返回值和传递到Set属性存取器参数值类型相同。Set属性存取器的返回值是void,而Get属性存取器没有入口参数。属性可以是静态的、虚拟的、抽象的、内部的、私有的、保护的或公共的。另外,属性可以在接口中定义,关于这一点将在后面讨论。
我还应该指出属性不必于字段关联。