特别的 .NET 类型成员

特别的 .NET 类型成员

 

发布日期: 8/23/2005 | 更新日期: 8/23/2005 出处:http://www.microsoft.com/china/MSDN/library/netFramework/netframework/msdnmagissues0102dotnet.mspx

Jeffrey Richter

介绍类型可以定义的一些特殊成员,这些成员支持良好的面向对象设计,同时大大简化了处理类型及其对象实例所需的语法。

*
本页内容
类型构造函数类型构造函数
属性属性
索引属性索引属性
小结小结

类型构造函数

您应该很熟悉构造函数,它负责设置对象实例的初始状态。除了实例构造函数,Microsoft® .NET 公共语言运行库(common language runtime,CLR)还支持类型构造函数(也称为静态构造函数、类构造函数、或者类型初始函数)。类型构造函数可以用于接口、类和值类型。它允许类型在任何成员被访问前进行所需的初始化工作。类型构造函数不接受任何参数,返回类型必须是 void。类型构造函数仅访问类型的静态字段,并且其通常的用途是初始化这些字段。类型构造函数确保在类型的任何实例创建前,以及在类型的任何静态字段或者方法被引用前运行。

许多语言(包括 C#)自动为任何定义的类型产生类型构造函数。但是,有些语言需要显式实现类型构造函数。

为了理解类型构造函数,查看下面的类型(用 C# 定义):

class AType {
   static int x = 5;
}

在生成该段代码时,编译器自动为 AType 产生一个类型构造函数。该构造函数负责将静态字段 x 的值初始化为 5。如果使用 ILDasm,可以很容易地发现类型构造函数方法,因为它们的名字是 .cctor(即 class constructor)。

在 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"; // Set the Name attribute
e.Age = 36;                 // Set the Age attribute
Console.WriteLine(e.Name);  // Displays "Jeffrey Richter" 

以这种方式对属性进行操作很常见。但是,我认为,上面的代码根本无法实现。面向对象设计和开发的一大特点是数据抽象。数据抽象意味着类型字段永远不应该被公开,因为太容易编写出不恰当使用字段的代码,破坏对象的状态。例如,很容易写出以下代码来破坏一个Employee对象。

e.Age = -5; // How could someone be -5 years old?

因此,当设计类型时,强烈建议将所有的字段设为私有,或者至少受保护 - 永远不要设为公共。这样,要让某类型的用户获取或者设置属性,公开专门用于此目的的方法。将对字段的访问封装起来的方法通常称为访问器方法 (accessor)。它们可以进行完整的检查(可选),保证对象的状态不会被破坏。例如,我会重写前面所示的 Employee 类,以模仿HYPERLINK "http://msdn.microsoft.com/msdnmag/issues/01/02/dotnet/figures.asp" /l "fig1"图 1 中的代码。这是一个简单的例子,但是可以看到抽象数据字段的巨大好处,也可以看到使属性只读或只写是多么容易,无需实现某种访问器方法。

图 1 中所示的抽象数据的方式有两个缺点。首先,因为必须实现额外的函数,所以需要编写更多的代码;其次,使用此类型的用户必须调用方法,而不是简单地引用一个字段名。

e.SetAge(36);    // Updates the age
e.SetAge(-5);    // Throws an exception 

我想您也会觉得这些缺点问题并不大。不过,运行库提供一种称为属性 (property) 的机制,可以部分解决第一个缺点并完全克服第二个缺点。

图 2 中所示的类使用了与图 1 中的类功能完全相同的属性。您可以看到,属性稍微简化了代码,更重要的是,它们允许调用方按如下方式编写代码:

e.Age = 36;    // Updates the age
e.Age = -5;    // Throws an exception

Get 属性访问器 (get property accessor) 返回的值与传递给 Set 属性访问器 (set property accessor) 的参数是同一类型。Set 属性具有返回类型 void,Get 属性没有任何参数。属性可以是静态函数,虚函数,抽象函数,内部函数,私有函数,保护函数或者公有函数。另外,后面会提到,属性可以在接口中定义。

还应该指出,属性不一定和某个字段相联系。例如,System.IO.FileStream 类型定义了一个 Length 属性,它返回流的字节总数。在字段中不保留该长度;相反,在调用 Length 属性的 get 方法时,它调用另一个函数,使底层操作系统返回打开文件流的字节总数。

当创建属性时,编译器实际上产生特别的 get_PropName 和/或 set_PropName 访问器方法(PropName 是属性的名称)。大多数编译器会理解这些特别的方法,从而允许开发人员使用特别的属性语法来访问这些方法。但是,不需要遵从通用语言规范(Common Language Specification,CLS)的编译器完全支持属性,编译器只需要支持调用这些特别的访问器方法。

另外,不完全支持属性的编译器可能需要稍微不同的语法来定义和使用属性。例如,托管扩展的 C++ 需要使用 _property 关键字。

索引属性

有些类型(例如 System.Collections.SortedList)公开元素的逻辑列表。为了轻松访问这种类型的元素,类型可以定义索引属性(也称为索引器,indexer)。图 3 显示索引属性的一个示例。使用这种类型索引器非常简单:

BitArray ba = new BitArray(14);
for (int x = 0; x < 14; x++) {
   // Turn all even numbered bits on
   ba[x] = (x % 2 == 0);
   Console.WriteLine("Bit " + x + " is " + (ba[x] ? "On" : "Off"));
}

图 3 所示的 BitArray 示例中,索引器有一个 Int32 的参数:bitPosition。所有索引器必须至少有一个参数,而且可能有两个或者更多的参数。这些参数(和返回类型)可以是任意类型。常见的情况是,创建一个将 String 作为参数的索引器,用于在一个关联数组中查找值。类型可以提供多个重载的索引器,只要它们的原型不同。

象设置属性一样,设置索引器访问器方法包含一个隐含的参数 value,它表示访问器被调用时新的期望值。BitArray 设置访问器显示这个使用的 value 参数。

一个设计良好的索引器应该同时具有 get 和 set 访问器。尽管您可以只实现 get 访问器(用于只读语义)或只实现 set 访问器(用于只写语义),建议索引器还是实现两个访问器。原因很简单,使用索引的用户不会希望只有一半的行为可用。例如,用户不希望在编写下面两行代码时出现编译器错误:

String s = SomeObj[5];  // Compiles OK if get accessor exists
SomeObj[5] = s;         // Compiler error if set accessor doesn't exist

索引器总是作用在特定的类型实例上,并且不能声明为静态的。但是索引器可以被标记为公有、私有、保护或者内部。

当创建一个索引器属性时,编译器实际上产生特殊的 get_item 和/或 set_item 访问器方法。大多数编译器会理解这些特殊的方法,从而允许开发人员使用特殊的索引属性语法访问这些方法。但是,不需要遵循 CLS 的编译器完全支持索引器属性;编译器只需要支持调用特殊的访问器方法。

而且,完全支持索引属性的编译器可能需要稍微不同的语法来定义和使用这些属性。例如,托管扩展的 C++需要使用 _property 关键字。

小结

本专栏讨论的概念对所有 .NET 程序员而言都非常重要。我提到的特殊类型的成员使组件成为公用语言运行库中最好的元素,即现代的组件旨在支持属性。

在下一个专栏中,我会介绍委托和事件成员,因为它们是基于组件的应用程序开发和设计的一个不可分割的部分。

Jeffrey Richter (http://www.jeffreyrichter.com/) 是 Programming Applications for Microsoft Windows (Microsoft Press, 1999) 的作者,也是 Wintellect (http://www.wintellect.com/) 公司的创始人之一。Wintellect 公司是一家软件教育、调试和咨询公司。他是 .NET 和 Win32® 领域的开发和设计专家。Jeff 现在正在撰写一本关于 Microsoft .NET Framework 开发的书籍,并且正在举办 .NET 技术研讨班。

翻译作者Luke是微软公司的软件工程师,习惯使用C++和C#开发应用程序。闲暇时间他喜欢音乐,旅游和怀旧游戏,并且愿意帮助MSDN翻译更多的文章和其他开发者共享。可以通过ecaijw@msn.com联系他。

转到原英文页面


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值