版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、NET-第3部分,第一部分 Microsoft.NET 框架基本原理 第二部分 类型与通用语言运行时 第三部分 类型设计 第四部分 基本类型 第五部分 类型管理,NET-第3部分,第三部分 类型设计 类型成员及其访问限定 常数与字段 方法 属性 事件,NET-第3部分,第7章 类型成员及其访问限定 本章内容: 展示如何使用各种不同的成员来设计一个类型。,NET-第3部分,7.1 类型成员,一个类型可以定义零个或多个以下成员:,常数 常数是一个表示恒定不变的数值的符号。这些符号主要用来使得代码更具可读性和可维护性。常数总是和类型而非它们的实例相关联。从这个意义上说,它们总是静态的。 字段 字段表
2、示一个数据的值,它或者是只读的,或者是可读可写的。字段还可分为静态字段和实例字段(非静态的)两种,静态字段被视为类型状态的一部分,实例字段被视为对象状态的一部分。 实例构造器 是一种特殊的方法,它用来将一个新对象的实例字段初始化到正常的初始状态。 类型构造器 是一种特殊的方法,它用来将一个类型的静态字段初始化到正常的初始状态。,NET-第3部分,方法 是一个函数,用来改变或查询一个类型(就静态方法而言),或者一个对象(就实例方法而言)的状态。方法一般需要读写类型或对象的字段。 重载操作符 也是一种方法,它用操作符的形式定义了怎样对对象进行某种操作。 转换操作符 也是一种方法,它定义了怎样将一个
3、对象从一种类型转换到另一种类型,这种转型可以是隐式的也可以是显式的。 属性 仍是一种方法,它以一种简单的、类似字段的方式实现了设置、或者查询一个类型或对象的状态,与此同时它又可以很好地保护它们的状态不会被破坏。,NET-第3部分,事件 事件分为静态事件和实例事件两种。静态事件通过类型发送通知,通知的接收者可以是一个类型,也可以是一个对象。实例事件通过对象发送通知,通知的接收者同样可以是一个类型,也可以是一个对象。事件通常在提供事件的类型或者对象的状态改变时被触发。事件包含两个方法,它们允许类型或对象(通常被称为“侦听者”)登记或者注销其感兴趣的“事件”。另外,事件采用委托字段来维护登记该事件的
4、侦听者集合。 类型 类型内部可以嵌套定义其他类型。这种策略通常用于将一个庞大复杂的类型划分成较小的代码块,从而达到简化实现的目的。,NET-第3部分,不管使用何种编程语言,编译器总是要先对我们的源码进行处理,然后为每一种成员产生相关的元数据,并为其中的方法成员产生 IL 代码。 元数据的格式和编程语言之间无任何关系。这种通用的元数据格式也为CLR所使用,CLR用它们在运行时决定常数、字段、构造器、方法、属性、以及事件的行为。 下面C#代码定义了一个包括所有可能成员的类型。展示编译器是怎样将这些类型和成员编译成元数据的。,NET-第3部分,using System; class SomeType
5、 class SomeNestedType const Int32 SomeConstant = 1; readonly Int32 SomeReadOnlyField = 2; static Int32 SomeReadWriteField = 3; static SomeType ( ) public SomeType ( ) public SomeType (Int32 x) String InstanceMethod ( ) return null; static void Main ( ) ,NET-第3部分,Int32 SomeProp get return 0; set publ
6、ic Int32 thisString s get return 0; set event EventHandler SomeEvent; 编译上面定义的类型,然后用ILDasm.exe来查看得到的元数据。,NET-第3部分,7.2 访问限定修饰符和预定义特性,访问限定修饰符指出了哪些类型和成员可以被其他的代码合法地引用。预定义特性则在访问限定修饰符的基础上为我们提供了更多的选择,并且允许我们改变一个成员的语义。 CLR定义了所有可能的访问限定修饰符集合,但是各种编程语言都选择了自己的面向开发人员的语法和术语。 下表显示了可以应用于类型、字段和方法的访问限定修饰符。其中Private定义的访问
7、限定最为严格,Public定义的最为广泛。,NET-第3部分,Private private Private 仅可以被所定义类型中的 方法访问 Family protected Protected 仅可以被所定义类型及其 派生类型中的方法访问, 与所在程序集无关 Family与Assembly (不支持) (不支持) 仅可以被所定义类型,及 与其同在一个程序集中的 派生类型中的方法访问 Assembly Internal Friend 仅可以被所定义程序集中 的方法访问 Family或Assembly protected internal Protected Friend 仅可以被所定义类型、
8、派 生类型以及任何定义在同 一程序集中的方法访问 Public public Public 可以被所有程序集中的所 有方法访问,应用于类型、字段、或方法的访问限定修饰符,CLR术语 C#术语 VisualBasic术语 描述,NET-第3部分,当设计一个类型或者成员时,我们只能选择一个访问限定修饰符。嵌套类型可以使用以上六种访问限定修饰符中的任何一种。而非嵌套类型则只能标识为 Public 或者 Assembly。如果一个非嵌套类型没有显式标识访问限定修饰符,C# 和 Visual Basic 将默认使用 Assembly (internal/Friend)来标识。 CLR 也定义了一个预定义
9、特性的集合,也允许各个语言为这些预定义特性选择使用自己的命名方式。,NET-第3部分,Abstract abstract MustInherit 不能被实例化。可以用作其他类型的基类 型。如果派生类型不是抽象的,则可以构 造它的实例 Sealed sealed NotInheritable 不能用作基类型,7.2.1 类型预定义特性,CLR术语 C#术语 VisualBasic术语 描述,CLR允许使用Abstract 或Sealed 来修饰一个类型,但两者不可以同时使用。,如果我们定义的类型仅包含静态成员,那么我们首先应该将此类型定义为Sealed,同时为它定义一个私有的无参构造器。定义私有
10、构造器会阻止C#编译器为类型自动产生公有的无参构造器。因为类型外的代码不能访问私有构造器,自然也就不能创建它的实例了。,NET-第3部分,Static static Shared 字段是类型状态、而非对象状态的一部分 InitOnly readonly ReadOnly 字段仅可以在构造器方法中被赋值,7.2.2 字段预定义特性,CLR术语 C#术语 VisualBasic术语 描述,CLR允许字段有3种标记:Static,InitOnly,或者同时使用 Static 和InitOnly。注意常数总被认为是 Static,也不能用 InitOnly 来标记。,NET-第3部分,Static s
11、tatic Shared 方法和类型、而非类型的实例相关。静态方 法不能访问类型中的实例字段或实例方法, 因为静态方法对对象的状态一无所知 Instance (默认) (默认) 方法和类型的实例而非类型本身关联。方法 可以访问实例字段和实例方法,也可以访问 静态字段和静态方法 Virtual virtual Overridable 当方法被调用时,无论对象是否被转换为其 基类型,都只有位于对象继承链最末端的方 法实现会被调用。仅应用于实例方法 Newslot new Shadows 方法的子类实现不会重写基类型中的实现, 而仅仅是将其隐藏起来。仅应用于虚方法,7.2.3 方法预定义特性,CLR
12、术语 C#术语 VisualBasic术语 描述,NET-第3部分,Override override Overrides 显式表明方法在重写基类型中的虚方法。仅 应用于虚方法 Abstract abstract MustOverride 表示派生类型必须提供和该抽象方法签名匹 配的实现。含有抽象方法的类型是一个抽象 类型。仅应用于虚方法 Final sealed NotOverridable 派生类型不能重写该方法。仅应用于虚方法,CLR术语 C#术语 VisualBasic术语 描述,NET-第3部分,第8章 常数与字段 本章内容: 介绍怎样向一个类型中添加数据成员。常数和字段。,NET-
13、第3部分,常数是一个表示恒定不变的值的符号。定义一个常数符号时,必须能够在编译时确定它的值。通过编译后,编译器将常数的值保存在其所定义模块的元数据内。 常数的类型只能是那些编译器认为的基元类型。常数总被认为是类型(而非实例)的一部分。 注:在C#中,下面的类型称为基元类型,可以被用来定义常数:Boolean、Char、Byte、SByte、Decimal、Int16、UInt16、Int32、UInt32、Int64、UInt64、Single、Double,以及String。(枚举类型由于本身以基元类型形式存储,故也可以被用来定义常数,虽然它并不是基元类型。),8.1 常 数,NET-第3部
14、分,当使用常数符号时,编译器首先从定义常数的模块的元数据中查找出该符号,直接取出常数的值,然后将之嵌入到编译后产生的IL代码中。因为常数的值是直接嵌入到代码中的,所以常数在运行时不再需要任何的内存分配。 不能获取常数的地址,或者以引用的方式来传递一个常数。 常数没有一个良好的跨模块版本特性。也就是说只有当确信一个符号的值永远不会改变时,才应该使用常数来定义它们。,NET-第3部分,首先将下面的代码编译成DLL程序集: using System; public class Component public const Int32 MaxEntriesInList = 50; 引用上面得到的程序集
15、,将下面代码编译成一个应用程序: using System; class App static void Main( ) Console.WriteLine(“Max entries supported in list: ” + Component.MaxEntriesInList); ,例子,NET-第3部分,当应用程序被编译时,编译器会发现MaxEntriesInList是一个值为50的常数符号,它将直接把整数值50嵌入到应用程序的IL代码中。实际上,在该应用程序代码完成编译后,先前引用的DLL程序集在程序运行时甚至不会被加载,而且完全可以将之从磁盘中删除。 常数隐含的版本问题。如果把前面
16、DLL程序集中的MaxEntriesInList常数值改为1000后并重新编译该DLL程序集,后面的应用程序代码将不会受到任何影响。要获得新的常数值,必须重新编译后面的应用程序。 如果要求一个模块中的数值能够在运行时(而不是编译时)被另一个模块获取,那么就不应该使用常数。相反,我们应该使用只读字段。,NET-第3部分,字段又称数据成员,它保存着一个值类型的实例、或者一个引用类型的引用。 CLR支持类型(静态)和实例(非静态)两种字段。对于类型字段,系统在该类型被加载进入一个应用程序域时为其分配动态内存,这通常发生在引用该类型的方法第一次被JIT编译时。对于实例字段,系统在该类型的实例被构建时为
17、其分配动态内存。 字段是以动态内存的形式存储的,因此只能在运行时刻获取它们的值。字段也没有常数的版本问题。另外,字段可以是任何类型,没有像常数那样的限制。 CLR支持只读和读写两种字段。编译器和IL代码验证器可以确保除了构造器外没有其他的方法会改写只读字段。,8.2 字 段,NET-第3部分,下面是新版DLL程序集的代码: using System; public class Component public static readonly Int32 MaxEntriesInList = 50; using System; class App static void Main( ) Cons
18、ole.WriteLine(“Max entries supported in list: ” + Component.MaxEntriesInList); ,用静态只读字段来解决“常数”示例中的版本问题,NET-第3部分,当运行应用程序中的Main方法时,CLR将加载DLL程序集(现在在运行时就需要该程序集)。然后从其动态内存中取出MaxEntriesInList字段。 假设将MaxEntriesInList字段的值从50改变为1000,并重新编译该DLL程序集。当再次执行应用程序时,它将自动获取修改后的值1000。,NET-第3部分,public class SomeType public
19、 static readonly Random random = new Random( ); static Int32 numberOfWrites = 0; public readonly String pathName = “Untitled”; public System.IO.FileStream fs; public SomeType (String pathName) this.pathName = pathName; public String DoSomething ( ) numberOfWrites = numberOfWrites + 1; return pathNam
20、e; ,定义静态读写字段,以及实例只读字段和实例读写字段,NET-第3部分,上述代码中,很多字段都是以内联的方式进行初始化的(这里内联的意思即为在声明字段的同时进行初始化赋值)。 C#允许采用这种方便的内联初始化语法来初始化一个类的常数、读写字段和只读字段。 C#对字段的内联初始化仅仅是一种简化的表达方式,实际上它们的初始化是在构造器内完成的。,NET-第3部分,第9章 方法 本章内容: 主要讨论一个类型中可以定义的各种不同方法,以及与这些方法相关的一些问题。怎样定义构造器方法(包括实例构造器和类型构造器)、操作符重载方法、转换操作符方法。怎样以引用的方式向方法传递参数,以及怎样定义接受可变数
21、目参数的方法。虚方法的版本机制。,NET-第3部分,实例构造器是一种特殊的方法,它们负责将类型的实例初始化到一个良好的状态。对于可验证的代码,CLR要求每个类(引用类型)至少定义一个实例构造器。 在创建一个引用类型的实例时,系统将执行以下三个步骤:首先为该实例分配内存,然后初始化对象的附加成员(即方法表指针和一个SyncBlockIndex),最后调用类型的实例构造器设置对象的初始状态。 所有构造器没有显式赋值的字段都保证有一个 0 或 null 的值。 默认情况下,对于引用类型,如果没有显式为其定义实例构造器,许多编译器都会为我们定义一个公有的无参构造器(通常称作默认构造器)。,9.1 实例
22、构造器,NET-第3部分,class SomeType / / C#编译器会自动定义一个默认的公有无参构造器 上面的类型定义等同于下面的类型定义: class SomeType public SomeType( ) 一个类型可以定义多个实例构造器。每个构造器都必须有一个不同的签名。多个构造器可以有不同的访问限制。 少数的几种情况下,类型实例的创建不需要调用实例构造器。例如,调用Object的MemberwiseClone方法。在反序列化一个对象时,通常也不会调用构造器。,NET-第3部分,C#提供了一种简单的语法来初始化字段: class SomeType Int32 x = 5; 当构造So
23、meType对象时,它的 x 字段将被初始化为5。这是怎样完成的呢?如果查看SomeType构造器方法(也称.ctor)的IL代码,会看到如下代码。,NET-第3部分,class SomeType Int32 x = 5; String s = “Hi there”; Double d = 3.14159; Byte b; public SomeType( ) public SomeType(Int32 x) public SomeType(String s) ; d = 10; 当编译器为以上三个构造器方法产生代码时,每一个方法的开始处都将包括初始化x、s、d的代码。在这些初始化代码之后,编
24、译器才会为各个构造器添加出现在其中的代码。,代码的膨胀效应,NET-第3部分,因为上面的类中有三个构造器,所以编译器共产生三次初始化x、s和d的代码每一个构造器一次。 如果我们有一些需要初始化的实例字段和许多重载的构造器方法,应该考虑在定义字段的时候避免同时对它们进行初始化,相反应该将这些公共的初始化语句放在一个初始化构造器中,然后使其他的构造器显式地调用这个初始化构造器。这有助于减少生成代码的尺寸。 看下面代码: class SomeType Int32 x; String s; Double d; Byte b;,NET-第3部分,public SomeType( ) x = 5; s =
25、 “Hi There!”; d = 3.14159; public SomeType(Int32 x) : this( ) this.x = x; public SomeType(String s) : this( ) this.s = s; ,NET-第3部分,CLR没有强制要求值类型中必须定义构造器方法。实际上,很多编译器都不会为值类型产生默认的无参构造器。原因是值类型可以被隐式的创建。看下面代码: struct Point public Int32 x, y; class Rectangle public Point topLeft, bottomRight; 要构造一个Rectangl
26、e实例,必须使用new操作符,并指定一个构造器。C#编译器自动产生的构造器将被调用。当系统为Rectangle实例分配完内存时,其中将包括两个Point值类型的实例。,值类型构造器,NET-第3部分,基于性能考虑,CLR不会尝试为每个包含在引用类型中的值类型字段调用构造器。但是,CLR会保证值类型中的所有字段都被初始化为0或者null。 虽然C#编译器不会为值类型自动产生默认的无参构造器,但是CLR却允许为值类型定义构造器。只有当显式地调用这些构造器的时候,它们才会执行。看下面代码: struct Point public Int32 x, y; public Point(Int32 x, I
27、nt32 y) this.x = x; this.y = y; ,NET-第3部分,class Rectangle public Point topLeft, bottomRight; public Rectangle( ) / / 对于C#来讲,在一个值类型上调用new仅仅只是调用 / / 构造器来初始化已经分配好内存的值类型 / / (注意与在引用类型上调用new时执行的3个步骤对比) topLeft = new Point(1, 2) ; bottomRight = new Point(100, 200) ; 如果 Rectangle 类的构造器中没有用 new 操作符来调用 Point
28、的构造器初始化它的 topLeft 和 bottomRight 字段的话,两个 Point 中的字段 x 和 y 都将保持为 0。,NET-第3部分,struct Point public Int32 x, y; public Point( ) x = y = 5; class Rectangle public Point topLeft, bottomRight; public Rectangle( ) 现在我们构造一个新的 Rectangle 实例时,猜猜两个 Point 字段topLeft 和 bottomRight 中的x和y将被初始化为什么?,例:为Point值类型定义默认的无参构造
29、器:,NET-第3部分,C#根本不允许为一个值类型定义无参构造器!所以前面的代码实际上根本就通不过编译。 虽然C#不允许值类型有无参构造器,但CLR却允许这样。所以如果需要这样的行为,可以使用另外一种编程语言(IL汇编语言)来为值类型定义无参构造器。 struct SomeValType Int32 x = 5; 由于可验证代码要求所有的值类型字段在被读之前首先进行初始化,所有为值类型定义的任何构造器都必须要初始化其所有的字段。 如下例:,NET-第3部分,struct SomeValType Int32 x,y; public SomeValType(Int32 x) this.x = x;
30、 编译时会产生错误:“error CS0171:在控制离开构造器之前,字段SomeValType必须完全赋值。”,NET-第3部分,除了实例构造器外,CLR还支持类型构造器(type constructor),也称为静态构造器(static constructor)、类构造器(class constructor)或者类型初始化器(type initializer)。 类型构造器适用于接口(但是C#编译器不支持)、引用类型和值类型。就像实例构造器用来设置类型的实例的初始状态一样,类型构造器用于设置一个类型的初始状态。 默认情况下,一个类型中没有定义类型构造器。如果要定义,也只能定义一个。并且,类
31、型构造器不能有任何参数。 下面演示了在C#中怎样为引用类型和值类型定义类型构造器:,9.2 类型构造器,NET-第3部分,class SomeRefType static SomeRefType( ) /当SomeRefType第一次被访问时执行 struct SomeValType /C#允许值类型定义无参类型构造器 static SomeValType( ) /当SomeRefType第一次被访问时执行 必须将类型构造器标记为static。另外,类型构造器的访问方式应该总为私有方式,这由C#编译器自动来完成。,NET-第3部分,class SomeRefType private stati
32、c SomeRefType( ) struct SomeValType private static SomeValType( ) 在代码中显式地将一个类型构造器标记为私有方式(或其他访问修饰符)的话,C#编译器会报告以下错误:“error CSo515:SomeValType():静态构造器中不允许出现访问修饰符”。,注意1:,NET-第3部分,internal struct SomeValType static SomeValType() Console.WriteLine(This never gets displayed); public Int32 m_x; public seale
33、d class Program public static void Main() SomeValTypea=new SomeValType10; a0.m_x=123; Console.WriteLine(a0.m_x); 即便可以在值类型中定义类型构造器,实际上永远不要这样做,因为CLR中存在不调用值类型的静态类型构造器的情况。,注意2:,/显式123,NET-第3部分,类型构造器的调用总是由CLR负责。另外,CLR选择调用类型构造器的时机也比较自由。CLR会选择下列时间之一来调用类型构造器: 在类型的第一个实例被创建之前,或者在类型的非继承字段或成员第一次被访问之前注意这个“之前”有一个
34、精确邻接(just before)的意思。因为CLR会在确定的时刻调用类型构造器,故这被称作精确语义(precise semantics)。 在非继承静态字段被第一次访问之前的某个时刻。因为CLR只保证静态构造器总是在静态字段被访问之前的某个时刻运行(可能运行的非常早),故这被称作字段初始化前语义(before-field-init semantics)。,类型构造器的调用,NET-第3部分,类型构造器一旦被执行,它在整个应用程序域的生命周期内都不会再次被调用。因为是CLR在负责调用类型构造器,所以应该避免编写需要以特殊顺序调用类型构造器的代码。 CLR只保证类型构造器开始执行它并不保证类型构
35、造器完成执行。 例如,ClassA中有一个类型构造器包含引用ClassB的代码,而且ClassB中也有一个类型构造器包含引用ClassA的代码。在这种情况下,CLR仍然可以确保每个类型构造器的代码只被执行一次;但是,CLR不能确保ClassA的类型构造器在执行ClassB的类型构造器之前完成。因此,需要尽量避免编写的代码出现这种情况。 最后,如果一个类型构造器抛出一个未处理的异常,CLR将认为该类型不可用。试图访问其中的任何字段或者方法都将抛出System.TypeInitializationException异常。,NET-第3部分,类型构造器中的代码只能访问类型的静态字段,并且通常它的目的
36、就是初始化这些静态字段。与实例字段一样,C#也提供了一种简单的初始化静态字段的语法: class SomeType static Int32 x = 5; struct SomeValType struct SomeValType Int32 x = 5; static Int32 x = 5; 注意 虽然C#不允许值类型使用内联字段初始化语法初始化实例字段,但允许用它来初始化静态字段。,NET-第3部分,当上述代码被编译时,编译器会为SomeType自动产生一个类型构造器。其产生的类型构造器和下面代码编译后的结果是等同的: class SomeType static Int32 x ; st
37、atic SomeType( ) x = 5; 使用ILDasm.exe可以很容易验证编译器为类型构造器产生的IL代码,如下图。类型构造器方法在方法定义元数据表中被称为.cctor。,NET-第3部分,在上图中可以看到.cctor 方法是一个私有静态方法。另外,方法中代码的含义为将 5 赋值给静态字段 x 。 类型构造器不应该调用其基类型的类型构造器。不需要这样做是因为基类型中的静态字段并没有被派生类型所继承。,NET-第3部分,最后,假设有以下代码: class SomeType static Int32 x = 5; static SomeType( ) x = 10; C#编译器会为其产
38、生一个类型构造器方法。该构造器首先将 x 初始化为 5 ,然后又将其改写为 10 。换句话说,当编译器为类型构造器产生IL代码时,它首先会产生初始化静态字段所需的代码,然后才是类型构造器方法中显式包含的代码转换而得的IL指令。,NET-第3部分,默认情况下,编译器选择对我们定义的类型最有意义的语义,并通过设置beforefieldinit元数据标记来告诉CLR这种选择。 C#编译器的选取情况以及选择结果如何影响类型构造器的性能。例:,类型构造器的性能,NET-第3部分,一些编程语言允许一个类型定义操作符来操作类型的实例。CLR对操作符重载一无所知,因为它甚至都不认识操作符是什么。相反,是我们选
39、择的编程语言定义了每个操作符的含义,以及当遇到它们时产生什么样的代码。 例如,在C#中,对基元数据类型应用+符号时将导致编译器产生将两个数加在一起的代码。将+符号应用到字符串对象时,C#编译器产生将两个字符串连接在一起的代码。对于判异操作,C#使用!=符号,而 Visual Basic使用符号。最后,符号在C#中的含义为异或操作(XOR),而在Visual Basic中意味着求幂操作。 虽然CLR对操作符一无所知,但它却规范了编程语言应该怎样提供操作符重载,以使它们可以很容易地被不同的编程语言编写的代码所使用。每个编程语言自己决定是否支持操作符重载,以及如果提供,表达和使用它们的语法是怎样的。
40、,9.3 操作符重载方法,NET-第3部分,编程语言选择了是否支持操作符重载以及它们的语法。当编译源代码时,编译器会产生一个方法来表示操作符的行为。 例如,假设定义了一个如下的类: class Complex public static Complex operator+(Complex c1, Complex c2) 编译器会产生一个名为op_Addition的方法定义;该方法定义条目上有一个specialname标记,表示这是一个“特殊”的方法。 当编译器看到源代码中的+操作符时,它们会去看其中的操作数类型中有哪一个定义了参数类型和操作数类型兼容、名为specialname方法。如果存着这
41、样的方法,编译器将产生调用该方法的代码。如果不存在,就会出现编译错误。,NET-第3部分,操作符重载是一个很有用的工具,它允许开发人员通过简洁的语法来表达自己的思想。然而,并不是所有的编程语言都支持操作符重载。所以当一个Visual Basic开发人员在一个非基元类型上应用+操作符时,编译器将产生一个错误,并停止编译代码。 问题:一个使用不支持操作符重载的语言的开发人员怎样调用另一个支持操作符重载的语言定义的类型呢? Visual Basic没有提供特殊的语法来让一个类型定义+操作符。也不知到怎样将使用+操作符的代码翻译成调用op_Addition方法的代码。然而,它支持调用一个类型的方法。所
42、以,在Visual Basic中,可以调用由C#编译器生成的类型中的op_Addition方法。 在Visual Basic中定义一个op_Addition方法,在C#中就可以用+操作符来调用?,9.3.1 操作符与语言互操作性,NET-第3部分,Imports System Public Class VBType Public Shared Function op_Addition(a as VBType, b as VBType) as VBType Return Nothing End Function End Class 下面是对两个VBType实例执行相加操作的C#应用程序: usi
43、ng System; public class CSharpApp public static void Main() VBType vb = new VBType(); / / vb = vb + vb; vb = VBType.op_Addition(vb, vb); ,NET-第3部分,using System; public class CSharpType public static CSharpType operator+(CSharpType a, CSharpType b) return null; 下面是对两个CSharpType实例执行相加操作的Visual Basic应用
44、程序: Import Systems Public Class VBApp Public Shared Sub Main() Dim cs as new CSharpType() cs = cs + cs cs = CSharpType.op_Addition(cs, cs) End Sub End Class,NET-第3部分,将一个类型的对象转化为另一个类型的对象。 当源类型和目标类型都是编译器认为的基元类型时,编译器知道怎样产生必要的代码来执行这样的转换。 如果有一个不是编译器认为的基元类型,编译器将不知道怎样执行转换。如:假定FCL中包含一个数据类型Rational。那么将Int32对
45、象或者Single对象转换为Rational对象将为编程工作带来不少方便。相应的将Rational对象转换为Int32对象或者Single对象也如此。 为了使这些转换成为可能,Rational类型应该定义一些公有的构造器,这些公有构造器的参数应该为我们希望转换的类型实例。另外,我们还应为Rational类型定义一些不接受任何参数的公有实例方法ToXxx,其中每一个方法将Rational类型的实例转换为Xxx类型。,9.4 转换操作符方法,NET-第3部分,class Rational /由一个Int32类型构造一个Rational类型 public Rational(Int32 numerat
46、or) . /由一个Single类型构造一个Rational类型 public Rational(Single value) . /将一个Rational类型转换为一个Int32类型 public Int32 ToInt32( ) . /将一个Rational类型转换为一个Single类型 public Single ToSingle( ) . 通过调用这些构造器和方法,使用任何编程语言开发的人员都可以将一个Int32或Single转换为一个Rational,或者将一个Rational转换为一个Int32或Single。,为Rational正确定义转换构造器及方法,NET-第3部分,转换操作符
47、本质上仍然是一些方法,它们可以将对象从一个类型转换为另一个类型。 下面代码演示了为Rational类型定义的4个转换操作符方法: class Rational /由一个Int32类型构造一个Rational类型 public Rational(Int32 numerator) . /由一个Single类型构造一个Rational类型 public Rational(Single value) . /将一个Rational类型转换为一个Int32类型 public Int32 ToInt32( ) . /将一个Rational类型转换为一个Single类型 public Single ToSin
48、gle( ) .,NET-第3部分,/由一个Int32类型隐式构建一个Rational类型并返回 public static implicit operator Rational(Int32 numerator) return new Rational(numerator); /由一个Single类型隐式构建一个Rational类型并返回 public static implicit operator Rational(Sinle value) return new Rational(value); /由一个Rational类型显式返回一个Int32类型 public static expli
49、cit operator Int32(Rational r) return r.ToInt32( ); /由一个Rational类型显式返回一个Single类型 public static explicit operator Single(Rational r) return r. ToSingle( ); ,NET-第3部分,和操作符重载方法一样,转换操作符方法也必须为 public 和 static 。但对于转换操作符方法,还必须告诉编译器是隐式地产生代码来调用转换操作符方法,还是要求源代码显式地告诉编译器产生代码来调用转换操作符方法。 C#中,用implicit关键字来告诉编译器,在源代
50、码中不必做显式的转型就可以产生调用转换操作符方法的代码;使用explicit关键字来告诉编译器只有当源代码中指定了显式的转型时,才产生调用转换操作符方法的代码。 指定operator关键字来告诉编译器该方法是一个转换操作符。在operator关键字后面,需要指定对象转型时的目标类型;在括号内,则需指定对象转型时的源类型。,NET-第3部分,前面为Rational类型定义的转换操作符允许我们像下面这样编写代码: class App static void Main( ) Rational r1 = 5; / / 将Int32 隐式转型为Rational Rational r2 = 2.5F; /
51、 / 将Single 隐式转型为Rational Int32 x = (Int32) r1; / / 将Rational显式转型为Int32 Single s = (Single) r2; / / 将Rational显式转型为Single 当C#编译器编译上面的代码时,它会检测到代码中的转型操作,并在内部产生调用Rational类型定义的转换操作符方法的IL代码。这些方法的名称是什么?,NET-第3部分,编译Rational类型,然后查看它的元数据,就可以发现编译器为类型中定义的每个转换操作符都生成了一个方法。至于Rational类型,它的4个转换操作符方法的元数据如下所示: public s
52、tatic Rational op_Implicit(Int32 numerator) public static Rational op_Implicit(Single numerator) public static Int32 op_Explicit(Rational r) public static Single op_Explicit(Rational r) 转换操作符方法的名称总为op_Implicit 或者op_Explicit 。对于转换过程中不可能丢失精度或数量级的转型操作,应该为其定义一个隐式的转换操作符;而对于转换过程中可能丢失精度或数量级的转型操作,应该为其定义一个显式
53、的转换操作符。,NET-第3部分,C#完全支持转换操作符。当编译器检测到源代码中正在使用某个类型的实例,而该处代码又期望的是另一种类型的实例时,它会去搜索能够执行这样的转换的隐式转换操作符方法。如果存在,编译器将产生调用该方法的IL代码。 当编译器检测到源代码中正在将一个类型的实例显式转型为另一个类型时,它会去搜索能够执行这样的转换的隐式或显式操作符方法。如果存在,编译器将产生调用该方法的IL代码。否则将,报告错误并停止编译。 System.Decimal类型,NET-第3部分,第10章 属性 本章内容: CLR提供了两种属性:无参属性和含参属性。前者通常就被称为属性(property),后者
54、在各种语言中有各自的叫法。,NET-第3部分,许多类型都定义了可被外部代码读取或者改变的状态信息。这些状态信息通常被实现为类型的字段成员。 例如,以下类型定义中就包含两个字段: public class Employee public String Name; /雇员姓名 public Int32 Age; /雇员年龄 如果创建了一个该类型的实例,可以很容易的读取或设置它的状态信息: Employee e = new Employee(); e.Name = “Jeffrey Richter”; /设置雇员姓名 e.Age = 35; /设置雇员年龄 Console.WriteLine(e.N
55、ame); /显示 Jeffrey Richter,10.1 无参属性,NET-第3部分,数据封装 设计类型时,将其所有字段的访问限制都设为私有方式,或者至少是保护方式永远不要设为公有方式,然后再以方法的形式让用户读取或设置对象的状态信息。 封装了对字段访问的方法典型地被称为访问器方法(accessor method)。访问器方法可以选择执行任何的数据合理性检查来确保对象的状态不会被破坏。,NET-第3部分,public class Employee private String Name; /私有字段 private Int32 Age; /私有字段 public String GetName() return(Name); public void SetName(String value) Name = value; public Int32 GetAge() return(Age); public void SetAge(Int32 value) if (value 0) throw new ArgumentOutOfRangeException( “Age must be gre
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论