c#

位置:IT落伍者 >> c# >> 浏览文章

.NET中的特殊类型成员


发布日期:2023年09月14日
 
.NET中的特殊类型成员

本文我们将考察类型能定义的某些特殊成员在大力简化处理类型及其对象实例需要的语法方面这些类型有助于面向对象设计

类型构造器

你已经熟悉了什么是构造器它负责对象实例状态的初始化除了实例构造器以外Microsoft(r)NET公共语言运行时(CLR)还支持类型构造器(也叫做静态构造器类构造器或类型初始化)类型构造器可被应用到接口类和数值类型它允许任何在类型中声明的成员被访问之前实现必要的初始化类型构造器不需要参数并且总是返回void类型类型构造器只访问类型的静态字段并且其通常的目的是初始化这些字段在类型的任何实例被创建之前以及类型的任何静态字段或方法被引用之前必须要保证已经运行了类型构造器

许多语言(包括C#)在定义类型时都自动产生类型构造器但是某些语言需要显式(手工)实现类型构造器

为了理解类型构造器让我们研究一下列在C#中定义的类型

class Atype {

static int x =

}

在建立这个代码时编译器自动地为产生Atype类型构造器这个构造器负责初始化静态字段x为值如果你使用ILDasm很容易认出类型构造器方法因为它们的名字都是cctor(对于类构造器而言)

在C#中通过在类型中定义静态构造器方法你可以自己实现类型构造器关键字static的使用意味着这时类型构造器而不是实例构造器下面是一个非常简单的例子

class AType {

static int x

static AType() {

x =

}

}

这个类型定义与前面的相同注意类型构造器决不能试图创建自己的类型实例而且构造器也不能引用类型的非静态成员

最后如果你用C#编译器编译下列代码它产生单独的类型构造器方法

class AType {

static int x =

static AType() {

x =

}

}

这个构造器首先初始化x=然后初始化x=换句话说编译器产生的结果类型构造器首先包含静态字段的初始化代码随后是类型构造器的代码

属性

许多类型定义的属性可以被重新获得或修改这些属性常常都是用类型字段成员来实现的例如下面是包含有两个字段的类型定义

class Employee {

public String Name

public Int Age

}

如果创建这个类型的实例那么很容易用以下代码得到或设置属性

Employee e = new Employee()

eName = Jeffrey Richter // 设置名字属性

eAge = // 设置年龄属性

ConsoleWriteLine(eName) // 显示 Jeffrey Richter

用这种方式使用属性非常普通但以我的观点看上述代码不会向列出的那样被实现面向对象设计和编程的立约之一便是数据抽象它的意思就时类型字段不能用公共字段暴露出来因为它太容易被修改太容易让人写出不恰当地使用这个字段的代码从而破坏对象的状态例如某人很容易编写下面的代码破坏Employee对象

eAge = //人的年龄怎么会是呢?

所以说在设计类型时我强烈建议所有字段都是私有的(private)或至少是受保护的(protected)——决不要公共的(public)然后让使用类型的人能Get或Set属性专门为此提供方法打包对字段的访问的方法就叫做存取器(或访问器方法)方法这些方法能随时实现完整性检查并保证对象的状态不被破坏例如我重写了前面定义过的Employee类代码如图一虽然这是一个简单的例子但你能从中明白抽象数据字段的巨大好处你还能从中明白如何轻松实现只读属性或者仅仅通过不去实现某个存取器方法来轻松达到只写属性

显示的数据抽象方法有两个缺点第一因为要实现附加的函数所以要多写一些代码第二类型的使用者现在必须要调用方法而不是仅仅引用单个的字段名

eSetAge( // Updates the age

eSetAge( // Throws an exception

我想所有的人都会同意这些缺点与其优点比起来显得微不足道但运行时仍然提供了一种属性机制多少使得第一个缺点容易忍受了并且完全消除了第二个缺点

类使用了属性其功能和上面所示的类相同正如你所看到的属性简化了一些代码但更重要的是允许调用这项下面一样写自己的代码

Age = // 更新年龄

eAge = // 掷出异常Throws an exception

Get属性存取器的返回值和传递到Set属性存取器参数值类型相同Set属性存取器的返回值是void而Get属性存取器没有入口参数属性可以是静态的虚拟的抽象的内部的私有的保护的或公共的另外属性可以在接口中定义关于这一点将在后面讨论

我还应该指出属性不必于字段关联例如类型SystemIOFileStream定义了一个长度属性它返回流中的字节数当长度属性的Get方法被调用时这个长度不是由字段提供而是调用另一个函数请求底层操作系统返回打开文件流的字节数

当你创建属性时编译器实际上发出专门的get_ProName和/或set_ProName存取器方法(这里ProName是属性名)大多数编译器会理解这些专用方法并允许开发人员存取这些有专门属性语法的方法但是遵守公共语言运行时规范的编译器不需要完全支持属性只要支持专用存取方法调用即可

另外对于完全支持属性的编译器来说在定义和使用属性时使用的语法稍有不同例如带受管扩展的C++需要使用_property关键字

索引属性

某些类型如SystemCollectionsSortedList暴露逻辑元素列表为了能轻松存取这种类型中的元素可以定义一个索引属性(也叫索引器indexer)一个索引属性的例子其索引器的的使用极其简单

BitArray ba = new BitArray(

for (int x = x < x++) {

// 置所有偶数位为on

ba[x] = (x % ==

ConsoleWriteLine(Bit + x + is + (ba[x] ? On Off))

}

BitArray例子中索引器带一个Int参数bitPosition索引器必须至少带一个参数参数个数可以是两个或更多这些参数(以及返回类型)可以是任何类型创建以String作为参数的索引器查找联合数组中的值是十分普通的事情一种类型可以提供多个索引器只要其原型不同

就像set属性set索引器存取方法包含一个隐藏的参数当存取方法被调用时它表示想得到一个新的值BitArray的set存取方法显示了这个参数值的使用

一个设计良好的索引器应该具备get和set两个存取方法即便你能只实现get存取方法(对于只读语义)或者只实现set存取方法(对于只写语义)建议你的索引器实现两个存取器理由很简单索引的使用者不希望只有半个行为例如当编写下面两行代码时使用者不想看到编译器出错

String s = SomeObj[] // 如果有存取器编译 OK

SomeObj[] = s //如果没有存取器编译出错

索引器总是起类型实例的作用并且不能被声明为静态但它可以是公共的私有的保护的或内部的

当你创建索引属性时编译器实际上会发布专门的get_Item和/或set_Item存取器方法大多数编译器都会理解这些专门的方法并且会允许开发人员利用专门的索引属性语法存取这些方法但是与CLS(公共语言系统)兼容的编译器不需要完全支持索引属性只要编译器支持专用存取器调用即可

同样对于完全支持索引属性的编译器在定义和使用这些属性的时候需要的语法稍有差别例如C++受管扩展需要使用_property关键字

结论

本文中所讨论的概念对于所有NET的程序员来说极其重要我所提到的特殊的类型成员使组件成为公共语言运行时最重要的内容也就是说现代组件被设计成支持属性

               

上一篇:.NET简谈反射(动态调用)

下一篇:I2C总线的模拟器的.Net Micro Framework实现