类型构造器
你已经熟悉了什么是构造器,它负责对象实例状态的初始化。除了实例构造器以外,Microsoft(r).NET公共语言运行时(CLR)还支持类型构造器(也叫做静态构造器、类构造器或类型初始化)。类型构造器可被应用到接口,类和数值类型。它允许任何在类型中声明的成员被访问之前实现必要的初始化。类型构造器不需要参数并且总是返回void类型。类型构造器只访问类型的静态字段并且其通常的目的是初始化这些字段。在类型的任何实例被创建之前以及类型的任何静态字段或方法被引用之前,必须要保证已经运行了类型构造器。
为了理解类型构造器,让我们研究一下列在C#中定义的类型:
class Atype {
static int x = 5;
}
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;
}
如果创建这个类型的实例,那么很容易用以下代码得到或设置属性:
e.Name = "Jeffrey Richter"; // 设置名字属性
e.Age = 36; // 设置年龄属性
Console.WriteLine(e.Name); // 显示 "Jeffrey Richter"
e.Age = -5; //人的年龄怎么会是-5呢?
e.SetAge(36); // Updates the age
e.SetAge(-5); // Throws an exception
类使用了属性,其功能和上面所示的类相同。正如你所看到的,属性简化了一些代码,但更重要的是允许调用这项下面一样写自己的代码:
.Age = 36; // 更新年龄
e.Age = -5; // 掷出异常Throws an exception
我还应该指出属性不必于字段关联。例如,类型System.IO.FileStream定义了一个长度属性,它返回流中的字节数。当长度属性的Get方法被调用时,这个长度不是由字段提供,而是调用另一个函数请求底层操作系统返回打开文件流的字节数。
另外,对于完全支持属性的编译器来说,在定义和使用属性时使用的语法稍有不同,例如带受管扩展的C++需要使用_property关键字。
索引属性
某些类型,如System.Collections.SortedList暴露逻辑元素列表。为了能轻松存取这种类型中的元素,可以定义一个索引属性(也叫索引器-indexer)。一个索引属性的例子,其索引器的的使用极其简单:
BitArray ba = new BitArray(14);
for (int x = 0; x < 14; x++) {
// 置所有偶数位为“on”
ba[x] = (x % 2 == 0);
Console.WriteLine("Bit " + x + " is " + (ba[x] ? "On" : "Off"));
}
BitArray例子中,索引器带一个Int32参数:bitPosition。索引器必须至少带一个参数,参数个数可以是两个或更多。这些参数(以及返回类型)可以是任何类型。创建以String作为参数的索引器查找联合数组中的值是十分普通的事情。一种类型可以提供多个索引器,只要其原型不同。
就像set属性,set索引器存取方法包含一个隐藏的参数,值,当存取方法被调用时,它表示想得到一个新的值。BitArray的set存取方法显示了这个参数值的使用。
一个设计良好的索引器应该具备get和set两个存取方法。即便你能只实现get存取方法(对于只读语义)或者只实现set存取方法(对于只写语义),建议你的索引器实现两个存取器。理由很简单,索引的使用者不希望只有半个行为。例如,当编写下面两行代码时,使用者不想看到编译器出错:
String s = SomeObj[5]; // 如果有存取器,编译 OK
SomeObj[5] = s; //如果没有存取器,编译出错
索引器总是起类型实例的作用,并且不能被声明为静态。但它可以是公共的、私有的、保护的或内部的。
当你创建索引属性时,编译器实际上会发布专门的get_Item和/或set_Item存取器方法。大多数编译器都会理解这些专门的方法并且会允许开发人员利用专门的索引属性语法存取这些方法。但是,与CLS(公共语言系统)兼容的编译器不需要完全支持索引属性;只要编译器支持专用存取器调用即可。
同样,对于完全支持索引属性的编译器在定义和使用这些属性的时候,需要的语法稍有差别。例如,C++受管扩展需要使用_property关键字。
结论
本文中所讨论的概念对于所有.NET的程序员来说极其重要。我所提到的特殊的类型成员使组件成为公共语言运行时最重要的内容。也就是说,现代组件被设计成支持属性。