第 4 章 c# 面向对象高级编程
DESCRIPTION
第 4 章 C# 面向对象高级编程. 前面介绍了面向对象程序设计的基本概念和应用。但是面向对象还包括很多其他重要的概念。本章将深入分析面向对象编程的概念,并详细说明利用工具进行面向对象程序设计的方法。. 4.1 类的继承与多态. 4.1.1 继承 1 、概述 现实世界中的许多实体之间不是相互孤立的,它们往往具有共同的特征,也存在内在的差别。人们可以采用层次结构来描述这些实体之间的相似之处和不同之处。 - PowerPoint PPT PresentationTRANSCRIPT
![Page 1: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/1.jpg)
第第 44 章 章 C#C# 面向对象高级编程面向对象高级编程
前面介绍了面向对象程序设计的基本概前面介绍了面向对象程序设计的基本概念和应用。但是面向对象还包括很多其他念和应用。但是面向对象还包括很多其他重要的概念。本章将深入分析面向对象编重要的概念。本章将深入分析面向对象编程的概念,并详细说明利用工具进行面向程的概念,并详细说明利用工具进行面向对象程序设计的方法。对象程序设计的方法。
![Page 2: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/2.jpg)
4.1 4.1 类的继承与多态类的继承与多态4.1.1 4.1.1 继承继承11 、概述、概述现实世界中的许多实体之间不是相互孤立的,它们往现实世界中的许多实体之间不是相互孤立的,它们往
往具有共同的特征,也存在内在的差别。人们可以采用层往具有共同的特征,也存在内在的差别。人们可以采用层次结构来描述这些实体之间的相似之处和不同之处。次结构来描述这些实体之间的相似之处和不同之处。
为了用软件语言对现实世界中的层次结构进行模型化,为了用软件语言对现实世界中的层次结构进行模型化,面向对象的程序设计技术引入了继承的概念。一个类从另面向对象的程序设计技术引入了继承的概念。一个类从另一个类派生出来时,派生类从基类那里继承特性。派生类一个类派生出来时,派生类从基类那里继承特性。派生类也可以作为其它类的基类。从一个基类派生出来的多层类也可以作为其它类的基类。从一个基类派生出来的多层类形成了类的层次结构。形成了类的层次结构。
注意:注意: C#C# 中,派生类只能从一个类中继承。这是因中,派生类只能从一个类中继承。这是因为,在为,在 C++C++ 中,人们在大多数情况下不需要一个从多个类中,人们在大多数情况下不需要一个从多个类中派生的类。从多个基类中派生一个类,这往往会带来许中派生的类。从多个基类中派生一个类,这往往会带来许多问题,从而抵消了这种灵活性带来的优势。多问题,从而抵消了这种灵活性带来的优势。
![Page 3: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/3.jpg)
C#C# 中,派生类从它的直接基类中继承成员:方法、域、属性、事件、中,派生类从它的直接基类中继承成员:方法、域、属性、事件、索引指示器。除了构造函数和析构函数,派生类隐式地继承了直接基索引指示器。除了构造函数和析构函数,派生类隐式地继承了直接基类的所有成员。类的所有成员。
C#C# 中,派生类从它的直接基类中继承成员:方法、域、属性、事件、索中,派生类从它的直接基类中继承成员:方法、域、属性、事件、索引指示器。除了构造函数和析构函数,派生类隐式地继承了直接基类引指示器。除了构造函数和析构函数,派生类隐式地继承了直接基类的所有成员。的所有成员。
程序清单:程序清单:using System;using System;namespace ConsoleApplication1namespace ConsoleApplication1{{
class Vehicle //class Vehicle // 定义汽车类定义汽车类{{
int wheels; //int wheels; // 公有成员公有成员 :: 轮子个数轮子个数protected float weight; //protected float weight; // 保护成员保护成员 :: 重量重量public Vehicle(){;}public Vehicle(){;}public Vehicle(int w, float g) public Vehicle(int w, float g) {{
wheels=w;wheels=w;weight=g;weight=g;
![Page 4: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/4.jpg)
}}public void Speak()public void Speak(){{
Console.WriteLine("the w vehicle is speaConsole.WriteLine("the w vehicle is speaking!");king!");
}}}}
class Car: Vehicle //class Car: Vehicle // 定义轿车类定义轿车类 :: 从汽车类中继承从汽车类中继承{ int passengers;//{ int passengers;// 私有成员私有成员 :: 乘客数乘客数
public Car(int w, float g, int p) : base(w,g)public Car(int w, float g, int p) : base(w,g){ wheels=w;{ wheels=w;
weight=g;weight=g;passengers=p;passengers=p;
}}}}
}}
![Page 5: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/5.jpg)
VehicleVehicle 作为基类,体现了“汽车”这个实体具有作为基类,体现了“汽车”这个实体具有的公共性质:汽车都有轮子和重量。 的公共性质:汽车都有轮子和重量。 CarCar 类继承类继承了了 VehicleVehicle 的这些性质,并且添加了自身的特性:的这些性质,并且添加了自身的特性:可以搭载乘客。可以搭载乘客。
C#C# 中的继承符合下列规则:中的继承符合下列规则:(( 11 )继承是可传递的。如果)继承是可传递的。如果 CC 从从 BB 中派生,中派生,
BB 又从又从 AA 中派生,那么中派生,那么 CC 不仅继承了不仅继承了 BB 中中声明的成员,同样也继承了声明的成员,同样也继承了 AA 中的成员。中的成员。ObjectObject 类作为所有类的基类。类作为所有类的基类。
(( 22)派生类应当是对基类的扩展。派生类)派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去己经继承可以添加新的成员,但不能除去己经继承的成员的定义。的成员的定义。
![Page 6: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/6.jpg)
(( 33)构造函数和析构函数不能被继承。除此以)构造函数和析构函数不能被继承。除此以外的其它成员,不论对它们定义了怎样的访问方外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。定派生类能否访问它们。
(( 44 )派生类如果定义了与继承而来的成员)派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖己继承的成员。同名的新成员,就可以覆盖己继承的成员。但这并不因为这派生类删除了这些成员,但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。只是不能再访问这些成员。
(( 55)类可以定义虚方法、虚属性以及虚索)类可以定义虚方法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。从而实现类可以展示出多态性。
![Page 7: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/7.jpg)
22、覆盖、覆盖 我们上面提到,类的成员声明中,可以我们上面提到,类的成员声明中,可以声明与继承而来的成员同名的成员。这时声明与继承而来的成员同名的成员。这时我们称派生类的成员覆盖我们称派生类的成员覆盖 (hide)(hide) 了基类的了基类的成员。这种情况下,编译器不会报告错误,成员。这种情况下,编译器不会报告错误,但会给出一个警告。对派生类的成员使用但会给出一个警告。对派生类的成员使用 nnewew 关键字,可以关闭这个警告。关键字,可以关闭这个警告。
前面汽车类的例子中,类前面汽车类的例子中,类 CarCar 继承了继承了 VeVehiclehicle 的的 Speak()Speak() 方法。我们可以给方法。我们可以给 CarCar 类类也声明一个也声明一个 Speak()Speak() 方法,覆盖方法,覆盖 VehicleVehicle 中中的的 SpeakSpeak ,见下面的代码。,见下面的代码。
程序清单:程序清单:
![Page 8: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/8.jpg)
using System;using System;
namespace ConsoleApplication1 namespace ConsoleApplication1 {{
class Vehicle//class Vehicle// 定义汽车类定义汽车类{{
public int wheels; //public int wheels; // 公有成员:轮子个数公有成员:轮子个数protected float weight;//protected float weight;// 保护成员:重量保护成员:重量public Vehicle(){;}public Vehicle(){;}public Vehicle(int w, float g) public Vehicle(int w, float g) {{
![Page 9: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/9.jpg)
wheels=w;wheels=w; weight=g; weight=g;}}
public void Speak() public void Speak() {{Console.WriteLine("the w vehicle is speaking!");Console.WriteLine("the w vehicle is speaking!");}}
}}class Car:Vehicle //class Car:Vehicle // 定义轿车类定义轿车类{{
int passengers;//int passengers;// 私有成员:乘客数私有成员:乘客数public Car(int w, float g, int p)public Car(int w, float g, int p){{wheels=w;wheels=w;weight=g;weight=g;
![Page 10: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/10.jpg)
passengers=p;passengers=p;
}}new public void Speak()new public void Speak(){{
Console.WriteLine("Di-di!");Console.WriteLine("Di-di!");}}
}}}} 注意:如果在成员声明中加上了注意:如果在成员声明中加上了 newnew 关关键字修饰,而该成员事实上并没有覆盖继键字修饰,而该成员事实上并没有覆盖继承的成员,编译器将会给出警告。在个成承的成员,编译器将会给出警告。在个成员声明同时使用员声明同时使用 newnew 和和 overrideoverride 则编译器则编译器会报告错误。会报告错误。
![Page 11: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/11.jpg)
33、、 basebase 保留字保留字
basebase关键字主要是为派生类调用基类成员提关键字主要是为派生类调用基类成员提供一个简写的方法。我们先看一个例子程序代码:供一个简写的方法。我们先看一个例子程序代码:
using System;using System;namespace ConsoleApplication1namespace ConsoleApplication1{{
class Aclass A{{
public void F()public void F(){{
//F//F的具体执行代码的具体执行代码}}
![Page 12: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/12.jpg)
public int this[int nIndex]public int this[int nIndex] {{
get{}get{}set{}set{}
}}class Bclass B{{
public void G()public void G(){{
int x=base[0];int x=base[0];base.F();base.F();
}}}}
}}}}
![Page 13: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/13.jpg)
类类 BB 从类从类 AA 中继承,中继承, BB 的方法的方法 GG中调用了中调用了 AA 的方法的方法FF和索引指示器。方法和索引指示器。方法 FF在进行编译时等价于:在进行编译时等价于:public void G()public void G(){{ int x=(A (this))[0];int x=(A (this))[0]; (A (this)).F();(A (this)).F();}}使用使用 basebase 关键字对基类成员的访问格式为:关键字对基类成员的访问格式为:base .base . 标识符标识符base [base [ 表达式列表表达式列表 ] ]
![Page 14: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/14.jpg)
4.1.2 4.1.2 多态多态
在面向对象的系统中,多态性在面向对象的系统中,多态性是一个非常重要的概念,它允许客是一个非常重要的概念,它允许客户对一个对象进行操作,由对象来户对一个对象进行操作,由对象来完成一系列的动作,具体实现哪个完成一系列的动作,具体实现哪个动作、如何实现由系统负责解释。动作、如何实现由系统负责解释。
![Page 15: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/15.jpg)
4.1.2.1 C#4.1.2.1 C# 中的多态中的多态““ 多态性”一词最早用于生物学,指同一种多态性”一词最早用于生物学,指同一种
族的生物体具有相同的特性。族的生物体具有相同的特性。在在 C#C# 中,多态性的定义是:同一操作作用中,多态性的定义是:同一操作作用
于不同的类的实例,不同的类将进行不同的解释,于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果。最后产生不同的执行结果。 C#C#支持两种类型的多支持两种类型的多态性:态性:
(( 11 )编译时的多态性)编译时的多态性编译时的多态性是通过重载来实现的。我们编译时的多态性是通过重载来实现的。我们
在前面介绍了方法重载,都实现了编译时的多态在前面介绍了方法重载,都实现了编译时的多态性。性。
对于非虚的成员来说,系统在编译时,根据对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信急决定实现何种操传递的参数、返回的类型等信急决定实现何种操作。作。
![Page 16: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/16.jpg)
(( 22)运行时的多态性)运行时的多态性
运行时的多态性就是指直到系统运行时,运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。才根据实际情况决定实现何种操作。 C#C# 中,中,运行时的多态性通过虚成员实现。运行时的多态性通过虚成员实现。
编译时的多态性为我们提供了运行速度编译时的多态性为我们提供了运行速度快的特点,而运行时的多态性则带来了高快的特点,而运行时的多态性则带来了高度灵活和抽象的特点。 度灵活和抽象的特点。
![Page 17: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/17.jpg)
4.1.2.2 4.1.2.2 虚方法虚方法
当类中的方法声明前加上了当类中的方法声明前加上了 virtualvirtual 修修饰符,我们称之为虚方法,反之为非虚。饰符,我们称之为虚方法,反之为非虚。使用了使用了 virtualvirtual 修饰符后,不允许再有修饰符后,不允许再有 statstatic, abstractic, abstract ,或,或 overrideoverride 修饰符。修饰符。
![Page 18: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/18.jpg)
对于非虚的方法,无论被其所在类的实例调对于非虚的方法,无论被其所在类的实例调用,还是被这个类的派生类的实例调用,方用,还是被这个类的派生类的实例调用,方法的执行方式不变。而对于虚方法,它的执法的执行方式不变。而对于虚方法,它的执行方式可以被派生类改变,这种改变是通过行方式可以被派生类改变,这种改变是通过方法的重载来实现的。方法的重载来实现的。
案例案例:虚方法与非虚方法的调用:虚方法与非虚方法的调用目标目标:说明了虚方法与非虚方法的区别:说明了虚方法与非虚方法的区别步骤步骤::11 、启动、启动 VS.NETVS.NET ,新建一个控制台应用程序,,新建一个控制台应用程序,名称填写为“名称填写为“ DifferentiateTest”DifferentiateTest” ,位置,位置设置为“设置为“ c:\CSharpSamples\chp4”c:\CSharpSamples\chp4” 。。
![Page 19: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/19.jpg)
22、在代码设计窗口中编辑、在代码设计窗口中编辑 Class1.csClass1.cs 。其中的代码。其中的代码编写如下:编写如下: using System;using System;namespace DifferentiateTestnamespace DifferentiateTest{ class A{ class A
{ public void F(){Console.WriteLine("A.F");}{ public void F(){Console.WriteLine("A.F");}public virtual void G(){Console.WriteLine("A.public virtual void G(){Console.WriteLine("A.
G");}G");}}}class B: Aclass B: A{ new public void F(){Console.WriteLine("B.F");}{ new public void F(){Console.WriteLine("B.F");}public override void G(){Console.WriteLine("B.G");}public override void G(){Console.WriteLine("B.G");}}}
![Page 20: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/20.jpg)
class Testclass Test{{
static void Main()static void Main(){{
B b=new B();B b=new B();A a=b;A a=b;a.F();a.F();b.F();b.F();a.G();a.G();b.G();b.G();
}}}}
}}
![Page 21: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/21.jpg)
33、按、按 Ctrl + F5Ctrl + F5编译并运行该程序,效果如图编译并运行该程序,效果如图 5-25-2所示。所示。
图 4-1 程序运行结果
![Page 22: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/22.jpg)
例子中,例子中, AA 类提供了两个方法:非虚的类提供了两个方法:非虚的 FF和虚方和虚方法法 GG。类。类 BB 则提供了一个新的非虚的方法则提供了一个新的非虚的方法 FF,,从而覆盖了继承的从而覆盖了继承的 FF ;类;类 BB 同时还重载了继承的同时还重载了继承的方法方法 GG。。在本例中,方法在本例中,方法 a.G()a.G() 实际调用了实际调用了 B.GB.G,而不是,而不是A.GA.G。这是因为编译时值为。这是因为编译时值为 AA ,但运行时值为,但运行时值为 BB ,,所以所以 BB完成了对方法的实际调用。完成了对方法的实际调用。
![Page 23: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/23.jpg)
4.1.2.3 4.1.2.3 在派生类中对虚方法进行重在派生类中对虚方法进行重载载 先让我们回顾一下普通的方法重载。普先让我们回顾一下普通的方法重载。普通的方法重载指的是:类中两个以上的方通的方法重载指的是:类中两个以上的方法(包括隐藏的继承而来的方法),取的法(包括隐藏的继承而来的方法),取的名字相同,只要使用的参数类型或者参数名字相同,只要使用的参数类型或者参数个数不同,编译器便知道在何种情况下应个数不同,编译器便知道在何种情况下应该调用哪个方法。 该调用哪个方法。
![Page 24: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/24.jpg)
而对基类虚方法的重载是函数重载的另一种特殊形而对基类虚方法的重载是函数重载的另一种特殊形式。在派生类中重新定义此虚函数时,要求的是方式。在派生类中重新定义此虚函数时,要求的是方法名称、返回值类型、参数表中的参数个数、类型、法名称、返回值类型、参数表中的参数个数、类型、顺序都必须与基类中的虚函数完全一致。在派生类顺序都必须与基类中的虚函数完全一致。在派生类中声明对虚方法的重载,要求在声明中加上中声明对虚方法的重载,要求在声明中加上 overridoverridee关键字,而且不能有关键字,而且不能有 new, staticnew, static或或 virtualvirtual修饰修饰符。符。
![Page 25: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/25.jpg)
4.1.3 4.1.3 抽象和密封抽象和密封
11 、抽象类、抽象类 有时候,基类并不与具体的事物相联系,有时候,基类并不与具体的事物相联系,
而是只表达一种抽象的概念,用以为它的而是只表达一种抽象的概念,用以为它的派生类提供一个公共的界面。为此,派生类提供一个公共的界面。为此, C#C# 中中引入了抽象类引入了抽象类 (abstract class)(abstract class) 的概念。的概念。
![Page 26: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/26.jpg)
注意:注意: C++C++ 程序员在这里最容易犯错误。程序员在这里最容易犯错误。 C++C++ 中中没有对抽象类进行直接声明的方法,而认为只要没有对抽象类进行直接声明的方法,而认为只要在类中定义了纯虚函数,这个类就是一个抽象类。在类中定义了纯虚函数,这个类就是一个抽象类。纯虚函数的概念比较晦涩,直观上不容易为人们纯虚函数的概念比较晦涩,直观上不容易为人们接受和掌握,因此接受和掌握,因此 C#C#抛弃了这一概念。抛弃了这一概念。
抽象类使用抽象类使用 abstractabstract 修饰符,对抽象类修饰符,对抽象类的使用有以下几点规定:的使用有以下几点规定:
(( 11 )抽象类只能作为其它类的基类,它不)抽象类只能作为其它类的基类,它不能直接被实例化,而且对抽象类不能使用能直接被实例化,而且对抽象类不能使用 nnewew 操作符。抽象类如果含有抽象的变量或操作符。抽象类如果含有抽象的变量或值,则它们要么是值,则它们要么是 nullnull 类型,要么包含了类型,要么包含了对非抽象类的实例的引用。对非抽象类的实例的引用。
(( 22)抽象类允许包含抽象成员,虽然这不)抽象类允许包含抽象成员,虽然这不是必须的。 是必须的。
![Page 27: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/27.jpg)
(( 33)抽象类不能同时又是密封的。)抽象类不能同时又是密封的。 如果一个非抽象类从抽象类中派生,则其必如果一个非抽象类从抽象类中派生,则其必
须通过重载来实现所有继承而来的抽象成员。请须通过重载来实现所有继承而来的抽象成员。请看下面的示例:看下面的示例:
using System;using System;namespace ConsoleApplication1namespace ConsoleApplication1{{
abstract class Aabstract class A{{
public abstract void F();public abstract void F();}}abstract class B: Aabstract class B: A{{
public void G(){}public void G(){}}}
![Page 28: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/28.jpg)
class C: Bclass C: B{{
public override void FDpublic override void FD{{
//F//F的具体实现代码的具体实现代码}}
}}}}
抽象类抽象类 AA 提供了一个抽象方法提供了一个抽象方法 FF。类。类 BB从抽象类从抽象类 AA 中继承,并且又提供了一个方中继承,并且又提供了一个方法法 GG ;因为;因为 BB 中并没有包含对中并没有包含对 FF的实现,的实现,所以所以 BB 也必须是抽象类。类也必须是抽象类。类 CC 从类从类 BB 中继中继承,类中重载了抽象方法承,类中重载了抽象方法 FF,并且提供了,并且提供了对对 FF的具体实现,则类的具体实现,则类 CC允许是非抽象的。允许是非抽象的。
![Page 29: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/29.jpg)
22、抽象方法、抽象方法由于抽象类本身表达的是抽象的概念,因此类中的许由于抽象类本身表达的是抽象的概念,因此类中的许
多方法并不一定要有具体的实现,而只是留出一个接日来多方法并不一定要有具体的实现,而只是留出一个接日来作为派生类重载的界面。举一个简单的例子,“图形”这作为派生类重载的界面。举一个简单的例子,“图形”这个类是抽象的,它的成员方法“计算图形面积”也就没有个类是抽象的,它的成员方法“计算图形面积”也就没有实际的意义。面积只对“图形”的派生类比如“圆”、实际的意义。面积只对“图形”的派生类比如“圆”、“一角形”这些非抽象的概念才有效,那么我们就可以把“一角形”这些非抽象的概念才有效,那么我们就可以把基类“图形”的成员方法“计算面积”声明为抽象的,具基类“图形”的成员方法“计算面积”声明为抽象的,具体的实现交给派生类通过重载来实现。体的实现交给派生类通过重载来实现。
一个方法声明中如果加上一个方法声明中如果加上 abstractabstract修饰符,我们称修饰符,我们称该方法为抽象方法该方法为抽象方法 (abstract method )(abstract method ) 。。
如果一个方法被声明也是抽象的,那么该方法默认也如果一个方法被声明也是抽象的,那么该方法默认也是一个虚方法。事实上,抽象方法是一个新的虚方法,它是一个虚方法。事实上,抽象方法是一个新的虚方法,它不提供具体的方法实现代码。我们知道,非虚的派生类要不提供具体的方法实现代码。我们知道,非虚的派生类要求通过重载为继承的虚方法提供自己的实现,而抽象方法求通过重载为继承的虚方法提供自己的实现,而抽象方法则不包含具体的实现内容,所以方法声明的执行体中只有则不包含具体的实现内容,所以方法声明的执行体中只有一个分号“一个分号“ ;”;” 。。
![Page 30: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/30.jpg)
只能在抽象类中声明抽象方法。对抽象方法,不只能在抽象类中声明抽象方法。对抽象方法,不能再使用能再使用 staticstatic或或 virtualvirtual修饰符,而且方法不能修饰符,而且方法不能有任何可执行代码,哪怕只是一对大括号中间加有任何可执行代码,哪怕只是一对大括号中间加一个一个分号“一个一个分号“ {;}”{;}” 都不允许出现,只需要给出都不允许出现,只需要给出方法的原型就可以了。方法的原型就可以了。
还要注意,抽象方法在派生类中不能使用还要注意,抽象方法在派生类中不能使用 basbasee关键字来进行访问。例如,下面的代码在编译关键字来进行访问。例如,下面的代码在编译时会发生错误:时会发生错误:
using System;using System; namespace ConsoleApplication1namespace ConsoleApplication1 {class A{class A
{public abstract void F();{public abstract void F(); }} class B: Aclass B: A {{
![Page 31: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/31.jpg)
public override void F()public override void F(){{
base.F();//base.F();//错误,错误, base.Fbase.F是抽是抽象方法象方法
}}}}
}} 我们还可以利用抽象方法来重载基类的我们还可以利用抽象方法来重载基类的虚方法,这时基类中虚方法的执行代码就虚方法,这时基类中虚方法的执行代码就被“拦截”了。下面的例子说明了这一点:被“拦截”了。下面的例子说明了这一点:
using System;using System;namespace ConsoleApplication1namespace ConsoleApplication1{class A{class A
{{
![Page 32: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/32.jpg)
public virtual void F()public virtual void F(){{
Console.WriteLine("A.F");Console.WriteLine("A.F");}}
}}abstract class B: Aabstract class B: A{{
public abstract override void F();public abstract override void F();}}class C: Bclass C: B{{
public override void F()public override void F(){{
Console.WriteLine("C.F");Console.WriteLine("C.F");}}
}}}}
![Page 33: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/33.jpg)
类类 AA声明了一个虚方法声明了一个虚方法 FF,派生类,派生类 BB使用抽象方使用抽象方法重载了法重载了 FF,这样,这样 BB 的派生类的派生类 CC就可以重载就可以重载 FF并并提供自己的实现。提供自己的实现。
33、密封类、密封类 想想看,如果所有的类都可以被继承,继承的想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果滥用会带来什么后果 ??类的层次结构体系将变得类的层次结构体系将变得十分庞大,类之间的关系杂乱无章,对类的理解十分庞大,类之间的关系杂乱无章,对类的理解和使用都会变得十分困难。有时候,我们并不希和使用都会变得十分困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类己望自己编写的类被继承。另一些时候,有的类己经没有再被继承的必要。经没有再被继承的必要。 C#C#提出了一个密封类提出了一个密封类(( sealed classsealed class )的概念,帮助开发人员来解决)的概念,帮助开发人员来解决这一问题。这一问题。
密封类在声明中使用密封类在声明中使用 sealedsealed修饰符,这样就修饰符,这样就可以防止该类被其它类继承。如果试图将一个密可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,封类作为其它类的基类, C#C# 将提不出错。理所当将提不出错。理所当然,密封类不能同时又是抽象类,因为抽象总是然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。希望被继承的。
![Page 34: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/34.jpg)
在哪些场合下使用密封类呢在哪些场合下使用密封类呢 ?? 密封类可以阻止其它密封类可以阻止其它程序员在无意中继承该类,而且密封类可以起到运程序员在无意中继承该类,而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生行时优化的效果。实际上,密封类中不可能有派生类,如果密封类实例中存在虚成员函数,该成员函类,如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符数可以转化为非虚的,函数修饰符 virtualvirtual 不再生不再生效。效。让我们看下面的例子:让我们看下面的例子: using System;using System; namespace ConsoleApplication1namespace ConsoleApplication1 {abstract class A{abstract class A
{public abstract void F();{public abstract void F(); }} sealed class B: Asealed class B: A {{
![Page 35: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/35.jpg)
public override void F()public override void F(){{
//F//F的具体实现代码的具体实现代码}}
}}}}
如果我们尝试写下面的代码:如果我们尝试写下面的代码:class C: B { }class C: B { }C#C# 会指出这个错误,告诉你会指出这个错误,告诉你 BB 是一个密封类,是一个密封类,
不能试图从不能试图从 BB 中派生任何类。中派生任何类。44 、密封方法、密封方法我们己经知道,使用密封类可以防止对类的继我们己经知道,使用密封类可以防止对类的继
承。承。 C#C# 还提出了密封方法还提出了密封方法 (sealed method)(sealed method) 的概念,的概念,以防止在方法所在类的派生类中对该方法的重载。以防止在方法所在类的派生类中对该方法的重载。
![Page 36: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/36.jpg)
对方法可以使用对方法可以使用 sealedsealed修饰符,这时我们称该方修饰符,这时我们称该方法是一个密封方法。法是一个密封方法。
不是类的每个成员方法都可以作为密封方法,不是类的每个成员方法都可以作为密封方法,密封方法必须对基类的虚方法进行重载,提供具密封方法必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,体的实现方法。所以,在方法的声明中, sealedsealed修饰符总是和修饰符总是和 overrideoverride修饰符同时使用。请看例修饰符同时使用。请看例子代码。子代码。
程序清单:程序清单:using System;using System;namespace ConsoleApplication1namespace ConsoleApplication1{{
class Aclass A{{
public virtual void F()public virtual void F(){{
![Page 37: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/37.jpg)
Console.WriteLine("A.F");Console.WriteLine("A.F"); }}
public virtual void G()public virtual void G(){ Console.WriteLine("A.G");{ Console.WriteLine("A.G");}}
}}class B: Aclass B: A{ sealed override public void F(){ sealed override public void F()
{ Console.WriteLine("B.F");{ Console.WriteLine("B.F");}}override public void G()override public void G(){ Console.WriteLine("B.G");{ Console.WriteLine("B.G");}}
}}class C: Bclass C: B{{
![Page 38: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/38.jpg)
override public void G()override public void G(){{
Console.WriteLine("C.G");Console.WriteLine("C.G");}}
}}}}
类类 BB 对基类对基类 AA 中的两个虚方法均进行了中的两个虚方法均进行了重载,其中重载,其中 FF方法使用了方法使用了 SealedSealed 修饰符,修饰符,成为一个密封方法。成为一个密封方法。 GG 方法不是密封方法,方法不是密封方法,所以在所以在 BB 的派生类的派生类 CC 中,可以重载方法中,可以重载方法 G,G,但不能重载方法但不能重载方法 FF。。
![Page 39: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/39.jpg)
4.2 4.2 操作符重载操作符重载4.2.1 4.2.1 问题的提出问题的提出在面向对象的程序设计中,自己定义一个类,就等于在面向对象的程序设计中,自己定义一个类,就等于
创建了一个新类型。类的实例和变量一样,可以作为参数创建了一个新类型。类的实例和变量一样,可以作为参数传递,也可以作为返回类型。传递,也可以作为返回类型。
在前几章中,我们介绍了系统定义的许多操作符。比在前几章中,我们介绍了系统定义的许多操作符。比如对于两个整型变量,使用算术操作符可以简便地进行算如对于两个整型变量,使用算术操作符可以简便地进行算术运算:术运算:
class Aclass A{{
public int x;public int x;public int y;public int y;public int Plus()public int Plus(){{
return x+y;return x+y;}}
}}
![Page 40: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/40.jpg)
再比如,我们希望将属于不同类的两个实例的数再比如,我们希望将属于不同类的两个实例的数据内容相加:据内容相加:
class Bclass B{ public int x;{ public int x;}}class Testclass Test{ public int z;{ public int z;
public static void Main()public static void Main(){{
A a=new A();A a=new A();B b=new B();B b=new B();z=a.x+b.x;z=a.x+b.x;
}}}}
![Page 41: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/41.jpg)
使用使用 a.x+b.xa.x+b.x这种写法不够简洁,也不够直观。更这种写法不够简洁,也不够直观。更为严重的问题是,如果类的成员在声明时使用的不为严重的问题是,如果类的成员在声明时使用的不是是 publicpublic修饰符的话,这种访问就是非法的。修饰符的话,这种访问就是非法的。我们知道,在我们知道,在 C#C# 中,所有数据要么属于某个类,中,所有数据要么属于某个类,要么属于某个类的实例,充分体现了面向对象的思要么属于某个类的实例,充分体现了面向对象的思想。因此,为了表达上的方便,人们希望可以重新想。因此,为了表达上的方便,人们希望可以重新给己定义的操作符赋予新的含义,在特定的类的实给己定义的操作符赋予新的含义,在特定的类的实例上进行新的解释。这就需要通过操作符重载来解例上进行新的解释。这就需要通过操作符重载来解决。决。
![Page 42: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/42.jpg)
4.2.2 4.2.2 使用成员方法重载操作符使用成员方法重载操作符
C#C# 中,操作符重载总是在类中进行声明,中,操作符重载总是在类中进行声明,并且通过调用类的成员方法来实现。并且通过调用类的成员方法来实现。
操作符重载声明的格式为:操作符重载声明的格式为:
![Page 43: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/43.jpg)
type operator operator-game (formal-param-listype operator operator-game (formal-param-list)t)C#C# 中,下列操作符都是可以重载的:中,下列操作符都是可以重载的: + - + - ! ! ~ ++ -- true false~ ++ -- true false * / % & | ^ << >> == != > < >= <= * / % & | ^ << >> == != > < >= <=
但也有一些操作符是不允许进行重载的,但也有一些操作符是不允许进行重载的,如:如:
== ,, &&&& ,, |||| ,, ?:?: ,, new, typeof , sinew, typeof , sizeof, iszeof, is◆◆ 一元操作符重载一元操作符重载顾名思义,一元操作符重载时操作符只顾名思义,一元操作符重载时操作符只
作用于一个对象,此时参数表为空,当前作用于一个对象,此时参数表为空,当前对象作为操作符的单操作数。对象作为操作符的单操作数。
![Page 44: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/44.jpg)
下面我们举一个角色类游戏中经常遇到的例子。扮下面我们举一个角色类游戏中经常遇到的例子。扮演的角色具有内力、体力、经验值、剩余体力、剩演的角色具有内力、体力、经验值、剩余体力、剩余内力五个属性,每当经验值达到一定程度时,角余内力五个属性,每当经验值达到一定程度时,角色便会升级,体力、内力上升,剩余体力和内力补色便会升级,体力、内力上升,剩余体力和内力补满。“升级”我们使用重载操作符“满。“升级”我们使用重载操作符“ ++”++” 来实现。来实现。
案例案例:游戏中“升级”问题:游戏中“升级”问题目标目标:掌握一元操作符重载的基本方法:掌握一元操作符重载的基本方法步骤步骤::11 、启动、启动 VS.NETVS.NET ,新建一个控制台应用程序,,新建一个控制台应用程序,名称填写为“名称填写为“ PlayerTest”PlayerTest” ,位置设置为,位置设置为““ c:\CSharpSamples\chp4”c:\CSharpSamples\chp4” 。。22、在代码设计窗口中编辑、在代码设计窗口中编辑 Class1.csClass1.cs 。其中。其中
的代码编写如下: 的代码编写如下:
![Page 45: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/45.jpg)
using System;using System;namespace PlayerTestnamespace PlayerTest
{ class Player{ class Player{ public int neili;{ public int neili;
public int tili;public int tili;public int jingyan;public int jingyan;public int neili_r;public int neili_r;public int tili_r;public int tili_r;public Player()public Player(){ neili=10;{ neili=10;
tili=50;tili=50;jingyan=0;jingyan=0;neili_r=50;neili_r=50;tili_r=50;tili_r=50;
}}public static Player operator ++(Player p)public static Player operator ++(Player p){{
![Page 46: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/46.jpg)
p.neili=p.neili+50;p.neili=p.neili+50; p.tili=p.tili+100; p.tili=p.tili+100; p.neili_r=p.neili; p.neili_r=p.neili; p.tili_r=p.tili; p.tili_r=p.tili; return p; return p;
}}public void Show()public void Show()
{ Console.WriteLine("Tili:{0} ",tili);{ Console.WriteLine("Tili:{0} ",tili);Console.WriteLine("Jingyan: {0}",jingyan);Console.WriteLine("Jingyan: {0}",jingyan);Console.WriteLine("Neili{0}",neili);Console.WriteLine("Neili{0}",neili);Console.WriteLine("Tili_full: {0}",tili_r);Console.WriteLine("Tili_full: {0}",tili_r);Console.WriteLine("Neili_full: {0}",neili_r);Console.WriteLine("Neili_full: {0}",neili_r);
}}}}
![Page 47: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/47.jpg)
class Testclass Test{{
public static void Main()public static void Main(){{
Player man=new Player();Player man=new Player();man.Show();man.Show();man++;man++;Console.WriteLine("Now upgraConsole.WriteLine("Now upgra
ding…:");ding…:");man. Show();man. Show();
}}}}
}}33、按、按 Ctrl + F5Ctrl + F5编译并运行该程序,效果如编译并运行该程序,效果如图图 4-24-2 所示。所示。
![Page 48: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/48.jpg)
◆◆二元操作符重载二元操作符重载 大多数情况下我们使用二元操作符重载。这时大多数情况下我们使用二元操作符重载。这时参数表中有一个参数,当前对象作为该操作符的参数表中有一个参数,当前对象作为该操作符的左操作数,参数作为操作符的右操作数。左操作数,参数作为操作符的右操作数。
图 4-2 程序运行结果
![Page 49: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/49.jpg)
下面我们给出二元操作符重载的一个简单例子。下面我们给出二元操作符重载的一个简单例子。案例案例:笛卡儿坐标相加问题:笛卡儿坐标相加问题目标目标:掌握二元操作符重载的基本方法:掌握二元操作符重载的基本方法步骤步骤::11 、启动、启动 VS.NETVS.NET,新建一个控制台应用程序,名称,新建一个控制台应用程序,名称填写为“填写为“ DKRTest”DKRTest” ,位置设置为“,位置设置为“ c:\CSharpc:\CSharpSamples\chp4”Samples\chp4” 。。22、在代码设计窗口中编辑、在代码设计窗口中编辑 Class1.csClass1.cs 。其中的代码。其中的代码
编写如下:编写如下:using System;using System;namespace DKRTestnamespace DKRTest{ class DKR{ class DKR
{ public int x,y,z;{ public int x,y,z;public DKR(int vx,int vy, int vz)public DKR(int vx,int vy, int vz){{
![Page 50: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/50.jpg)
x=vx;x=vx; y=vy; y=vy; z=vz; z=vz; } }
public static DKR operator +(DKR d1,DKR d2)public static DKR operator +(DKR d1,DKR d2){ DKR dkr=new DKR(0,0,0);{ DKR dkr=new DKR(0,0,0);
dkr.x=d1.x+d2.x;dkr.x=d1.x+d2.x;dkr.y=d1.y+d2.y;dkr.y=d1.y+d2.y;dkr.z=d1.z+d2.z;dkr.z=d1.z+d2.z;return dkr;return dkr;
}}}}class Testclass Test{ public static void Main(){ public static void Main()
{{
![Page 51: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/51.jpg)
DKR d1=new DKR(3,2,1);DKR d1=new DKR(3,2,1);DKR d2=new DKR(0,6,5);DKR d2=new DKR(0,6,5);DKR d3=d1+d2;DKR d3=d1+d2;Console.WriteLine("The 3d locaConsole.WriteLine("The 3d loca
tion of d3 is: {0}, {1}, {2}",d3.x,d3.y,d3.z);tion of d3 is: {0}, {1}, {2}",d3.x,d3.y,d3.z);}}
}}}}33、按、按 Ctrl + F5Ctrl + F5编译并运行该程序,效果如编译并运行该程序,效果如图图 4-34-3 所示所示
图 4-3 程序运行结果
![Page 52: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/52.jpg)
4.3 4.3 类型转换类型转换 转换是使一种类型的表达式可以被视为转换是使一种类型的表达式可以被视为
另一种类型。转换可以是隐式或显式,这另一种类型。转换可以是隐式或显式,这将确定是否需要显式地强制转换。例如,将确定是否需要显式地强制转换。例如,从从 intint 类型到类型到 longlong 类型的转换是隐式的,类型的转换是隐式的,因此因此 intint 类型的表达式可隐式地按类型的表达式可隐式地按 longlong 类类型处理。从型处理。从 longlong 类型到类型到 intint 类型的反向转类型的反向转换是显式的,因此需要显式地强制转换。换是显式的,因此需要显式地强制转换。
int a = 123;int a = 123; long b = a; // long b = a; // 从 从 int int 类型到 类型到 long long 类型类型
的转换的转换 int c = (int) b; // int c = (int) b; // 从 从 long long 类型到 类型到 int int 类类
型的反向转换型的反向转换
![Page 53: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/53.jpg)
4.3.1 4.3.1 隐式类型转换隐式类型转换下列转换属于隐式转换:下列转换属于隐式转换:◆◆标识转换 标识转换 ◆◆隐式数值转换 隐式数值转换 ◆◆隐式枚举转换 隐式枚举转换 ◆◆隐式常数表达式转换 隐式常数表达式转换 ◆◆用户定义的隐式转换 用户定义的隐式转换 隐式转换可以在各种情况下发生,包括函数成隐式转换可以在各种情况下发生,包括函数成
员调用、强制转换表达式以及赋值。员调用、强制转换表达式以及赋值。11 、标识转换是在同一类型(可为任何类型)内进、标识转换是在同一类型(可为任何类型)内进
行转换。这种转换的存在,仅仅是为了使已具有行转换。这种转换的存在,仅仅是为了使已具有所需类型的实体可被认为是可转换的(转换为该所需类型的实体可被认为是可转换的(转换为该类型)。类型)。22、隐式数值转换为: 、隐式数值转换为:
![Page 54: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/54.jpg)
◆◆从从 sbytesbyte到到 shortshort 、、 intint 、、 longlong 、、 floatfloat 、、 doubledouble或或 dedecimalcimal 。。◆从◆从 bytebyte到到 shortshort 、、 ushortushort 、、 intint 、、 uintuint 、、 longlong 、、 ulonulongg 、、 float doublefloat double或或 decimaldecimal 。。◆从◆从 shortshort到到 intint 、、 longlong 、、 floatfloat 、、 doubledouble或或 decimaldecimal 。。◆从◆从 ushortushort到到 intint 、、 uintuint 、、 longlong 、、 ulongulong 、、 floatfloat 、、 doudoubleble或或 decimaldecimal 。。◆从◆从 intint到到 longlong 、、 floatfloat 、、 doubledouble或或 decimaldecimal 。。◆从◆从 uintuint到到 longlong 、、 ulongulong 、、 floatfloat 、、 doubledouble或或 decimaldecimal 。。
◆从◆从 longlong到到 floatfloat 、、 doubledouble或或 decimaldecimal 。。◆从◆从 ulongulong到到 floatfloat 、、 doubledouble或或 decimaldecimal 。。◆从◆从 charchar 到到 ushortushort 、、 intint 、、 uintuint 、、 longlong 、、 ulongulong 、、 floatfloat 、、doubledouble或或 decimaldecimal 。。◆从◆从 floatfloat到到 doubledouble 。。 从从 intint 、、 uintuint 、、 longlong或或 ulongulong到到 floatfloat 以及以及
从从 longlong或或 ulongulong到到 doubledouble 的转换可能导致精度的转换可能导致精度损失,但决不会影响到它的数量级。其他的隐式损失,但决不会影响到它的数量级。其他的隐式数值转换决不会丢失任何信息。数值转换决不会丢失任何信息。
![Page 55: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/55.jpg)
不存在向不存在向 charchar 类型的隐式转换,因此其它整型的值类型的隐式转换,因此其它整型的值不会自动转换为不会自动转换为 charchar 类型。类型。33、隐式枚举转换允许将十进制整数、隐式枚举转换允许将十进制整数 00 转换为任何转换为任何枚举类型。枚举类型。44 、隐式常数表达式转换允许进行以下转换:、隐式常数表达式转换允许进行以下转换: ◆◆intint 类型的常数表达式可以转换为类型的常数表达式可以转换为 sbytesbyte 、、 bytebyte 、、
shortshort 、、 ushortushort 、、 uintuint或或 ulongulong 类型(前提是常类型(前提是常数表达式的值在目标类型的范围内)。 数表达式的值在目标类型的范围内)。 ◆◆longlong 类型的常数表达式可以转换为类型的常数表达式可以转换为 ulongulong 类型类型
(前提是常数表达式的值不为负)。(前提是常数表达式的值不为负)。55、用户定义的隐式转换由以下三部分组成:先是、用户定义的隐式转换由以下三部分组成:先是
一个标准的隐式转换(可选);然后是执行用户一个标准的隐式转换(可选);然后是执行用户定义的隐式转换运算符,最后是另一个标准的隐定义的隐式转换运算符,最后是另一个标准的隐式转换(可选)。计算用户定义的转换的精确规式转换(可选)。计算用户定义的转换的精确规则的说明。则的说明。
![Page 56: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/56.jpg)
从从 SS 类型到类型到 TT类型的用户定义的隐式转换按下面这类型的用户定义的隐式转换按下面这样处理:样处理:11 )查找类型集)查找类型集 DD,将从该类型集考虑用户定义的,将从该类型集考虑用户定义的转换运算符。此集合由转换运算符。此集合由 SS (如果(如果 SS 是类或构造)、是类或构造)、SS 的基类(如果的基类(如果 SS 是类)和是类)和 TT(如果(如果 TT是类或结是类或结构)组成。 构)组成。 22)查找适用的用户定义转换运算符集合)查找适用的用户定义转换运算符集合 UU。集合。集合UU由用户定义的隐式转换运算符组成,这些运算由用户定义的隐式转换运算符组成,这些运算符是在符是在 DD中的类或结构内声明的,用于从包含中的类或结构内声明的,用于从包含 SS的类型转换为被的类型转换为被 TT包含的类型。如果包含的类型。如果 UU为空,则为空,则转换未定义并且发生编译时错误。转换未定义并且发生编译时错误。33)在)在 UU中查找运算符的最精确的源类型中查找运算符的最精确的源类型 SXSX::①①如果如果 UU中的所有运算符都从中的所有运算符都从 SS转换,则转换,则 SXSX为为 SS 。。②②否则,否则, SXSX在在 UU中运算符的合并目标类型集中是中运算符的合并目标类型集中是被包含程度最大的类型。如果找不到这样的被包被包含程度最大的类型。如果找不到这样的被包含程度最大的类型,则转换是不明确的,并且发含程度最大的类型,则转换是不明确的,并且发生编译时错误。生编译时错误。
![Page 57: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/57.jpg)
44 )在)在 UU中查找运算符的最精确的目标类型中查找运算符的最精确的目标类型 TXTX::①①如果如果 UU中的所有运算符都转换为中的所有运算符都转换为 TT,则,则 TXTX为为 TT。。②②否则,否则, TXTX在在 UU中运算符的合并目标类型集中是包含程度中运算符的合并目标类型集中是包含程度最大的类型。如果找不到这样的包含程度最大的类型,则最大的类型。如果找不到这样的包含程度最大的类型,则转换是不明确的,并且发生编译时错误。转换是不明确的,并且发生编译时错误。
55)如果)如果 UU中正好含有一个从中正好含有一个从 SXSX转换到转换到 TXTX的用户定义转换的用户定义转换运算符,则这就是最精确的转换运算符。如果不存在此类运算符,则这就是最精确的转换运算符。如果不存在此类运算符,或者如果存在多个此类运算符,则转换是不明确运算符,或者如果存在多个此类运算符,则转换是不明确的,并且发生编译时错误。否则,将应用用户定义的转换:的,并且发生编译时错误。否则,将应用用户定义的转换:
①①如果如果 SS 不是不是 SXSX,则先执行一个从,则先执行一个从 SS到到 SXSX的标准隐式转的标准隐式转换。换。
②②调用最精确的用户定义转换运算符,以从调用最精确的用户定义转换运算符,以从 SXSX转换到转换到 TXTX。。③③如果如果 TXTX不是不是 TT,则再执行一个,则再执行一个 TXTX到到 TT的标准隐式转换。的标准隐式转换。
![Page 58: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/58.jpg)
4.3.24.3.2 显式类型转换显式类型转换下列转换属于显式转换:下列转换属于显式转换:◆◆显式数值转换显式数值转换◆◆显式枚举转换显式枚举转换◆◆用户定义的显式转换用户定义的显式转换显式转换可在强制转换表达式中出现。显式转换可在强制转换表达式中出现。显式转换集包括所有隐式转换。这意味着允许使用显式转换集包括所有隐式转换。这意味着允许使用冗余的强制转换表达式。冗余的强制转换表达式。
不是隐式转换的显式转换是这样的一类转换:它不是隐式转换的显式转换是这样的一类转换:它们不能保证总是成功,知道有可能丢失信息,变们不能保证总是成功,知道有可能丢失信息,变换前后的类型显著不同,以至值得使用显式表示换前后的类型显著不同,以至值得使用显式表示法。法。
11 、显式数值转换是指从一个数值类型到另一个数、显式数值转换是指从一个数值类型到另一个数值转换的转换,此转换不能用已知的隐式数值转值转换的转换,此转换不能用已知的隐式数值转换实现,它包括:换实现,它包括:
![Page 59: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/59.jpg)
◆◆从从 sbytesbyte到到 bytebyte 、、 ushortushort 、、 uintuint 、、 ulongulong 或或 charchar 。。◆从◆从 bytebyte到到 sbyte sbyte 和和 charchar 。。◆◆从从 shortshort到到 sbytesbyte 、、 bytebyte 、、 ushortushort 、、 uintuint 、、 ulongulong或或 chch
arar 。。◆◆从从 ushortushort到到 sbytesbyte 、、 bytebyte 、、 shortshort或或 charchar 。。◆◆从从 intint到到 sbytesbyte 、、 bytebyte 、、 shortshort 、、 ushortushort 、、 uintuint 、、 ulongulong或或 charchar 。。
◆◆从从 uintuint到到 sbytesbyte 、、 bytebyte 、、 shortshort 、、 ushortushort 、、 intint或或 charchar 。。◆◆从从 longlong到到 sbytesbyte 、、 bytebyte 、、 shortshort 、、 ushortushort 、、 intint 、、 uintuint 、、
ulongulong或或 charchar 。。◆◆从从 ulongulong到到 sbytesbyte 、、 bytebyte 、、 shortshort 、、 ushortushort 、、 intint 、、 uintuint 、、
longlong或或 charchar 。。◆◆从从 charchar到到 sbytesbyte 、、 bytebyte或或 shortshort 。。◆◆从从 floatfloat到到 sbytesbyte 、、 bytebyte 、、 shortshort 、、 ushortushort 、、 intint 、、 uintuint 、、
longlong 、、 ulongulong 、、 charchar或或 decimaldecimal 。。◆◆从从 doubledouble到到 sbytesbyte 、、 bytebyte 、、 shortshort 、、 ushortushort 、、 intint 、、 uiui
ntnt 、、 longlong 、、 ulongulong 、、 charchar 、、 floatfloat或或 decimaldecimal 。。◆◆从从 decimaldecimal到到 sbytesbyte 、、 bytebyte 、、 shortshort 、、 ushortushort 、、 intint 、、 uu
intint 、、 longlong 、、 ulongulong 、、 charchar 、、 floatfloat或或 doubledouble 。。
![Page 60: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/60.jpg)
由于显式转换包括所有隐式和显式数值转换,因此总是由于显式转换包括所有隐式和显式数值转换,因此总是可以使用强制转换表达式从任何数值类型转换为任何其他的可以使用强制转换表达式从任何数值类型转换为任何其他的数值类型。数值类型。 显式数值转换有可能丢失信息或导致引发异常。显式显式数值转换有可能丢失信息或导致引发异常。显式
数值转换按下面所述处理:数值转换按下面所述处理:◆◆对于从一个整型到另一个整型的转换,处理取决于该转换对于从一个整型到另一个整型的转换,处理取决于该转换发生时的溢出检查上下文:发生时的溢出检查上下文:
11 )在)在 checkedchecked 上下文中,如果源操作数的值在目标类型的上下文中,如果源操作数的值在目标类型的范围内,转换就会成功,但如果源操作数的值在目标类型范围内,转换就会成功,但如果源操作数的值在目标类型的范围外,则会引发的范围外,则会引发 System.OverflowExceptionSystem.OverflowException 。。
22)在)在 uncheckedunchecked上下文中,转换总是会成功并按下面所述上下文中,转换总是会成功并按下面所述进行。进行。
如果源类型大于目标类型,则截断源值(截去源值中如果源类型大于目标类型,则截断源值(截去源值中容不下的最高有效位)。然后将结果视为目标类型的值。容不下的最高有效位)。然后将结果视为目标类型的值。
如果源类型小于目标类型,则源值或按符号扩展或按如果源类型小于目标类型,则源值或按符号扩展或按零扩展,以使它的大小与目标类型相同。如果源类型是有零扩展,以使它的大小与目标类型相同。如果源类型是有符号的,则使用按符号扩展;如果源类型是无符号的,则符号的,则使用按符号扩展;如果源类型是无符号的,则使用按零扩展。然后将结果视为目标类型的值。使用按零扩展。然后将结果视为目标类型的值。
![Page 61: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/61.jpg)
如果源类型的大小与目标类型相同,则源值被视为目标如果源类型的大小与目标类型相同,则源值被视为目标类型的值。类型的值。
◆◆对于从对于从 decimaldecimal到整型的转换,源值向零到整型的转换,源值向零舍入到最接近的整数值,该整数值成为转换的结舍入到最接近的整数值,该整数值成为转换的结果。如果转换得到的整数值不在目标类型的范围果。如果转换得到的整数值不在目标类型的范围内,则会引发内,则会引发 System.OverflowExceptionSystem.OverflowException 。。◆◆对于从对于从 floatfloat或或 doubledouble到整型的转换,处到整型的转换,处
理取决于发生该转换时的溢出检查上下文:理取决于发生该转换时的溢出检查上下文:11 )在)在 checkedchecked上下文中,如下所示进行转上下文中,如下所示进行转
换:换:如果操作数的值是如果操作数的值是 NaNNaN 或无穷大,则引发或无穷大,则引发 SySy
stem.OverflowExceptionstem.OverflowException 。 。 否则,源操作数会向零舍入到最接近的整数否则,源操作数会向零舍入到最接近的整数
值。如果该整数值处于目标类型的范围内,则该值。如果该整数值处于目标类型的范围内,则该值就是转换的结果。 值就是转换的结果。 否则,引发否则,引发 System.OverflowExceptionSystem.OverflowException 。。
![Page 62: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/62.jpg)
22)在)在 uncheckedunchecked上下文中,转换总是会成功并按上下文中,转换总是会成功并按下面这样继续。下面这样继续。
如果操作数的值是如果操作数的值是 NaNNaN 或或 infiniteinfinite ,则转换的结果,则转换的结果是目标类型的一个未经指定的值。 是目标类型的一个未经指定的值。
否则,源操作数会向零舍入到最接近的整数值。如果否则,源操作数会向零舍入到最接近的整数值。如果该整数值处于目标类型的范围内,则该值就是转换的结果。该整数值处于目标类型的范围内,则该值就是转换的结果。
否则,转换的结果是目标类型的一个未经指定的值。否则,转换的结果是目标类型的一个未经指定的值。◆◆对于从对于从 doubledouble到到 floatfloat 的转换,的转换, doubledouble值舍入值舍入
到最接近的到最接近的 floatfloat值。如果值。如果 doubledouble值过小,无法表示为值过小,无法表示为 flfloatoat值,则结果变成正零或负零。如果值,则结果变成正零或负零。如果 doubledouble值过大,值过大,无法表示为无法表示为 float float 值,则结果变成正无穷大或负无穷大。值,则结果变成正无穷大或负无穷大。如果如果 double double 值为 值为 NaNNaN,则结果仍然是,则结果仍然是 NaNNaN。 。
◆◆对于从对于从 floatfloat或或 doubledouble到到 decimaldecimal 的转换,源值的转换,源值转换为用转换为用 decimaldecimal 形式来表示,并且在需要时,将它在第形式来表示,并且在需要时,将它在第2828位小数位数上舍入到最接近的数字。如果源值过小,位小数位数上舍入到最接近的数字。如果源值过小,无法表示为无法表示为 decimaldecimal ,则结果变成零。如果源值为 ,则结果变成零。如果源值为 NaNNaN、、无穷大或者太大而无法表示为无穷大或者太大而无法表示为 decimaldecimal值,则将引发值,则将引发 SysSystem.OverflowExceptiontem.OverflowException 。。
![Page 63: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/63.jpg)
◆◆对于从对于从 decimaldecimal到到 floatfloat或或 doubledouble 的转换,的转换, decimaldecimal值值舍入到最接近的舍入到最接近的 doubledouble或或 floatfloat值。虽然这种转换可能会损值。虽然这种转换可能会损
失精度,但决不会导致引发异常。失精度,但决不会导致引发异常。 22、显式枚举转换为:、显式枚举转换为:◆◆从从 sbytesbyte 、、 bytebyte 、、 shortshort 、、 ushortushort 、、 intint 、、 uintuint 、、
longlong 、、 ulongulong 、、 charchar 、、 floatfloat 、、 doubledouble或或 decimal decimal 到到任何枚举类型。 任何枚举类型。
◆◆从任何枚举类型到从任何枚举类型到 sbytesbyte 、、 bytebyte 、、 shortshort 、、 ushushortort 、、 intint 、、 uintuint 、、 longlong 、、 ulongulong 、、 charchar 、、 floatfloat 、、 dodoubleuble或或 decimaldecimal 。 。
◆◆从任何枚举类型到任何其他枚举类型。从任何枚举类型到任何其他枚举类型。两种类型之间的显式枚举转换是通过将任何参与的枚两种类型之间的显式枚举转换是通过将任何参与的枚
举类型都按该枚举类型的基础类型处理,然后在结果类型举类型都按该枚举类型的基础类型处理,然后在结果类型之间执行隐式或显式数值转换。例如,给定具有之间执行隐式或显式数值转换。例如,给定具有 intint 基础基础类型的枚举类型类型的枚举类型 EE,从,从 EE 到到 bytebyte 的转换按从的转换按从 intint到到 bytebyte的显式数值转换处理,而从的显式数值转换处理,而从 bytebyte到到 EE的转换按从的转换按从 bytebyte到到intint 的隐式数值转换处理。的隐式数值转换处理。
![Page 64: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/64.jpg)
33、用户定义的显式转换由以下三个部分组成:先、用户定义的显式转换由以下三个部分组成:先是一个标准的显式转换(可选),然后是执行用户是一个标准的显式转换(可选),然后是执行用户定义的隐式或显式转换运算符,最后是另一个标准定义的隐式或显式转换运算符,最后是另一个标准的显式转换(可选)。的显式转换(可选)。从从 SS 类型到类型到 TT类型的用户定义的显式转换按下面这类型的用户定义的显式转换按下面这样处理: 样处理:
11 )查找类型集)查找类型集 DD,将从该类型集考虑用户定义的,将从该类型集考虑用户定义的转换运算符。该类型集由转换运算符。该类型集由 SS (如果(如果 SS 为类或结为类或结构)、构)、 SS 的基类(如果的基类(如果 SS 为类)、为类)、 TT(如果(如果 TT为为类或结构)和类或结构)和 TT的基类(如果的基类(如果 TT为类)组成。为类)组成。22)查找适用的用户定义转换运算符集合)查找适用的用户定义转换运算符集合 UU。集合。集合UU由用户定义的隐式或显式转换运算符组成,这由用户定义的隐式或显式转换运算符组成,这些运算符是在些运算符是在 DD中的类或结构内声明的,用于从中的类或结构内声明的,用于从包含包含 SS或被或被 SS 包含的类型转换为包含包含的类型转换为包含 TT 或被或被 TT包包含的类型。如果含的类型。如果 UU为空,则转换未定义并且发生为空,则转换未定义并且发生编译时错误。编译时错误。33)在)在 UU中查找运算符的最精确的源类型中查找运算符的最精确的源类型 SXSX::
![Page 65: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/65.jpg)
①①如果如果 UU中的所有运算符都从中的所有运算符都从 SS转换,则转换,则 SXSX为为SS 。。②否则,如果②否则,如果 UU中的所有运算符都从包含中的所有运算符都从包含 SS 的类的类型转换,则型转换,则 SXSX在这些运算符的合并源类型集中是在这些运算符的合并源类型集中是被包含程度最大的类型。如果找不到最直接包含被包含程度最大的类型。如果找不到最直接包含的类型,则转换是不明确的,并且发生编译时错的类型,则转换是不明确的,并且发生编译时错误。误。③否则,③否则, SXSX在在 UU中运算符的合并源类型集中是中运算符的合并源类型集中是包含程度最大的类型。如果找不到这样的包含程包含程度最大的类型。如果找不到这样的包含程度最大的类型,则转换是不明确的,并且发生编度最大的类型,则转换是不明确的,并且发生编译时错误。译时错误。44 )在)在 UU中查找运算符的最精确的目标类型中查找运算符的最精确的目标类型 TXTX::
①①如果如果 UU中的所有运算符都转换为中的所有运算符都转换为 TT,则,则 TXTX为为 TT。。②②否则,如果否则,如果 UU中的所有运算符都转换为被中的所有运算符都转换为被 TT包含包含
的类型,则的类型,则 TXTX在这些运算符的合并源类型集中是在这些运算符的合并源类型集中是包含程度最大的类型。如果找不到这样的包含程包含程度最大的类型。如果找不到这样的包含程度最大的类型,则转换是不明确的,并且发生编度最大的类型,则转换是不明确的,并且发生编译时错误。译时错误。
![Page 66: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/66.jpg)
③③否则,否则, TXTX在在 UU中运算符的合并目标类型集中中运算符的合并目标类型集中是被包含程度最大的类型。如果找不到这样的被是被包含程度最大的类型。如果找不到这样的被包含程度最大的类型,则转换是不明确的,并且包含程度最大的类型,则转换是不明确的,并且发生编译时错误。发生编译时错误。
55)如果)如果 UU中正好含有一个从中正好含有一个从 SXSX转换到转换到 TXTX的用户的用户定义转换运算符,则这就是最精确的转换运算符。定义转换运算符,则这就是最精确的转换运算符。如果不存在此类运算符,或者如果存在多个此类如果不存在此类运算符,或者如果存在多个此类运算符,则转换是不明确的,并且发生编译时错运算符,则转换是不明确的,并且发生编译时错误。否则,将应用用户定义的转换:误。否则,将应用用户定义的转换:①①如果如果 SS 不是不是 SXSX,则先执行一个从,则先执行一个从 SS到到 SXSX的标的标准显式转换。准显式转换。②②调用最精确的用户定义转换运算符,以从调用最精确的用户定义转换运算符,以从 SXSX转转换到换到 TXTX。。③③如果如果 TXTX不是不是 TT,则再执行一个从,则再执行一个从 TXTX到到 TT的标的标准显式转换。准显式转换。
![Page 67: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/67.jpg)
标准显式转换包括所有的标准隐式转换以及一个显标准显式转换包括所有的标准隐式转换以及一个显式转换的子集,该子集是由那些与已知的标准隐式式转换的子集,该子集是由那些与已知的标准隐式转换反向的转换组成的。换言之,如果存在一个从转换反向的转换组成的。换言之,如果存在一个从AA 类型到类型到 BB 类型的标准隐式转换,则一定存在与其类型的标准隐式转换,则一定存在与其对应的两个标准显式转换(一个是从对应的两个标准显式转换(一个是从 AA 类型到类型到 BB 类类型,另一个是从型,另一个是从 BB 类型到类型到 AA 类型)。类型)。4.3.34.3.3 类的引用转换类的引用转换11 、隐式引用转换为:、隐式引用转换为:◆◆从任何引用类型到从任何引用类型到 objectobject 。。◆◆从任何类类型从任何类类型 SS到任何类类型到任何类类型 TT(前提是(前提是 SS 是从是从 TT派派
生的)。生的)。◆◆从任何类类型从任何类类型 SS到任何接口类型到任何接口类型 TT(前提是(前提是 SS 实现了实现了TT)。)。◆◆从任何接口类型从任何接口类型 SS到任何接口类型到任何接口类型 TT(前提是(前提是 SS 是从是从TT派生的)。派生的)。◆◆从元素类型为从元素类型为 SESE的数组类型的数组类型 SS到元素类型为到元素类型为 TETE的数的数组类型组类型 TT(前提是以下所列的条件均为真): (前提是以下所列的条件均为真):
![Page 68: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/68.jpg)
11 )) SS 和和 TT只是元素类型不同。换言之,只是元素类型不同。换言之, SS 和和 TT具具有相同的维数。有相同的维数。22)) SESE和和 TETE都是引用类型。都是引用类型。33)存在从)存在从 SESE 到到 TETE的隐式引用转换。的隐式引用转换。
◆◆从任何数组类型到从任何数组类型到 System.ArraySystem.Array 。。◆◆从任何委托类型到从任何委托类型到 System.DelegateSystem.Delegate 。。◆◆从从 nullnull 类型到任何引用类型。类型到任何引用类型。隐式引用转换是指一类引用类型之间的转换,隐式引用转换是指一类引用类型之间的转换,
这种转换总是可以成功,因此不需要在运行时进这种转换总是可以成功,因此不需要在运行时进行任何检查。行任何检查。
引用转换无论是隐式的还是显式的,都不会引用转换无论是隐式的还是显式的,都不会更改所转换的对象的引用标识。换言之,虽然引更改所转换的对象的引用标识。换言之,虽然引用转换可能改变该引用的类型,但决不会更改所用转换可能改变该引用的类型,但决不会更改所引用对象的类型或值。引用对象的类型或值。
![Page 69: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/69.jpg)
22、显式引用转换为:、显式引用转换为: ◆◆从从 objectobject到任何其他引用类型。到任何其他引用类型。◆◆从任何类类型从任何类类型 SS到任何类类型到任何类类型 TT(前提是(前提是 SS 为为 TT的基的基
类)。类)。◆◆从任何类类型从任何类类型 SS到任何接口类型到任何接口类型 TT(前提是(前提是 SS未密封并未密封并
且且 SS 不实现不实现 TT)。)。◆◆从任何接口类型从任何接口类型 SS到任何类类型到任何类类型 TT(前提是(前提是 TT未密封或未密封或 TT
实现实现 SS )。)。◆◆从任何接口类型从任何接口类型 SS到任何接口类型到任何接口类型 TT(前提是(前提是 SS 不是从不是从 TT
派生的)。派生的)。◆◆从元素类型为从元素类型为 SESE的数组类型的数组类型 SS到元素类型为到元素类型为 TETE 的数组的数组
类型类型 TT(前提是以下所列条件均为真):(前提是以下所列条件均为真):11 )) SS 和和 TT只是元素类型不同。换言之,只是元素类型不同。换言之, SS 和和 TT具有相同的具有相同的维数。维数。
22)) SESE和和 TETE 都是引用类型。都是引用类型。33)存在从)存在从 SESE 到到 TETE 的显式引用转换。的显式引用转换。
![Page 70: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/70.jpg)
◆◆从从 System.ArraySystem.Array 以及它实现的接口到任何数以及它实现的接口到任何数组类型。组类型。◆从◆从 System.DelegateSystem.Delegate 以及它实现的接口到任何以及它实现的接口到任何委托类型。委托类型。显式引用转换是那些需要运行时检查以确保显式引用转换是那些需要运行时检查以确保
它们正确的引用类型之间的转换。它们正确的引用类型之间的转换。为了使显式引用转换在运行时成功,源操作为了使显式引用转换在运行时成功,源操作
数的值必须为数的值必须为 nullnull ,或源操作数所引用的对象的,或源操作数所引用的对象的实际类型必须是一个可通过隐式引用转换转换为实际类型必须是一个可通过隐式引用转换转换为目标类型的类型。如果显式引用转换失败,则将目标类型的类型。如果显式引用转换失败,则将引发引发 System.InvalidCastExceptionSystem.InvalidCastException 。。
引用转换无论是隐式的还是显式的,都不会引用转换无论是隐式的还是显式的,都不会更改被转换的对象的引用标识。换言之,虽然引更改被转换的对象的引用标识。换言之,虽然引用转换可能更改引用的类型,但决不会更改所引用转换可能更改引用的类型,但决不会更改所引用对象的类型或值。用对象的类型或值。
![Page 71: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/71.jpg)
4.3.44.3.4 装箱与拆箱装箱与拆箱装箱转换允许将值类型隐式转换为引用类型。装箱转换允许将值类型隐式转换为引用类型。
将值类型的一个值装箱包括以下操作:分配一个将值类型的一个值装箱包括以下操作:分配一个对象实例,然后将值类型的值复制到该实例中。对象实例,然后将值类型的值复制到该实例中。
装箱转换允许将“值类型”隐式转换为“引装箱转换允许将“值类型”隐式转换为“引用类型”。存在下列装箱转换:用类型”。存在下列装箱转换:
◆◆从任何“值类型”(包括任何“枚举类从任何“值类型”(包括任何“枚举类型”)到类型型”)到类型 objectobject 。。
◆◆从任何“值类型”(包括任何“枚举类从任何“值类型”(包括任何“枚举类型”)到类型型”)到类型 System.ValueTypeSystem.ValueType 。。
◆◆从任何“值类型”到“值类型”实现的任从任何“值类型”到“值类型”实现的任何“接口类型”。何“接口类型”。
◆◆从任何“枚举类型”到从任何“枚举类型”到 System.EnumSystem.Enum 类类型。型。
将“值类型”的值装箱的操作包括:分配一将“值类型”的值装箱的操作包括:分配一个对象实例并将“值类型”的值复制到该实例中。个对象实例并将“值类型”的值复制到该实例中。
![Page 72: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/72.jpg)
最能说明“值类型”的值的实际装箱过程的办法是,最能说明“值类型”的值的实际装箱过程的办法是,设想有一个为该类型设置的装箱类。对任何“值类设想有一个为该类型设置的装箱类。对任何“值类型”的型”的 TT而言,装箱类的行为可用下列声明来描述:而言,装箱类的行为可用下列声明来描述:
sealed class T_Box: System.ValueTypesealed class T_Box: System.ValueType{{
T value;T value;public T_Box(T t) public T_Box(T t) {{
value = t;value = t;}}
}}
![Page 73: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/73.jpg)
TT类型值类型值 vv 的装箱过程现在包括执行表达式的装箱过程现在包括执行表达式 new T_new T_Box(v)Box(v) 和将结果实例作为和将结果实例作为 objectobject 类型的值返回。因类型的值返回。因此,下面的语句:此,下面的语句:
int i = 123;int i = 123;object box = i;object box = i;在概念上相当于:在概念上相当于:int i = 123;int i = 123;object box = new int_Box(i);object box = new int_Box(i);实际上,像上面这样的实际上,像上面这样的 T_BoxT_Box和和 int_Boxint_Box并并
不存在,并且装了箱的值的动态类型也不会真的不存在,并且装了箱的值的动态类型也不会真的属于一个类类型。相反,属于一个类类型。相反, TT类型的装了箱的值属类型的装了箱的值属于动态类型于动态类型 TT,若用,若用 isis运算符来检查动态类型的运算符来检查动态类型的话,也仅能引用类型话,也仅能引用类型 TT。例如,。例如,
![Page 74: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/74.jpg)
int i = 123;int i = 123;object box = i;object box = i;if (box is int) if (box is int) { Console.Write("Box contains an int");{ Console.Write("Box contains an int");}}
将在控制台上输出字符串“将在控制台上输出字符串“ Box contains an int”Box contains an int” 。。装箱转换隐含着复制一份欲被装箱的值。这不同于装箱转换隐含着复制一份欲被装箱的值。这不同于
从引用类型到 从引用类型到 object object 类型的转换,在后一种转换中,转类型的转换,在后一种转换中,转换后的值继续引用同一实例,只是将它当作派生程度较小换后的值继续引用同一实例,只是将它当作派生程度较小的的 objectobject 类型而已。例如,设有下列的声明:类型而已。例如,设有下列的声明:
struct Pointstruct Point{ public int x, y;{ public int x, y; public Point(int x, int y) public Point(int x, int y) { this.x = x;{ this.x = x; this.y = y;this.y = y; }}}}
![Page 75: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/75.jpg)
则下面的语句:则下面的语句:Point p = new Point(10, 10);Point p = new Point(10, 10);object box = p;object box = p;p.x = 20;p.x = 20;Console.Write(((Point)box).x);Console.Write(((Point)box).x);
将在控制台上输出值将在控制台上输出值 1010,因为将,因为将 pp 赋值赋值给给 boxbox 是一个隐式装箱操作,它将复制是一个隐式装箱操作,它将复制 pp 的的值。如果将值。如果将 PointPoint 声明为声明为 classclass ,由于,由于 pp 和和boxbox 将会引用同一个实例,因此输出值为将会引用同一个实例,因此输出值为 2020。。拆箱也称为取消装箱转换。取消装箱转拆箱也称为取消装箱转换。取消装箱转
换允许将引用类型显式转换为值类型。一个换允许将引用类型显式转换为值类型。一个取消装箱操作包括以下两个步骤:首先检查取消装箱操作包括以下两个步骤:首先检查对象实例是否为给定值类型的一个装了箱的对象实例是否为给定值类型的一个装了箱的值,然后将该值从实例中复制出来。值,然后将该值从实例中复制出来。
![Page 76: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/76.jpg)
取消装箱转换允许将引用类型显式转换为值类型。取消装箱转换允许将引用类型显式转换为值类型。存在以下取消装箱转换:存在以下取消装箱转换:◆◆从类型 从类型 object object 到任何值类型(包括任何枚到任何值类型(包括任何枚
举类型)。 举类型)。 ◆◆从类型 从类型 System.ValueType System.ValueType 到任何值类型到任何值类型
(包括任何枚举类型)。 (包括任何枚举类型)。 ◆◆从任何接口类型到实现了该接口类型的任何从任何接口类型到实现了该接口类型的任何
值类型。 值类型。 ◆◆从 从 System.Enum System.Enum 类型到任何枚举类型。 类型到任何枚举类型。 取消装箱操作包括以下两个步骤:首先检查该取消装箱操作包括以下两个步骤:首先检查该
对象实例是否是某个给定的值类型的装了箱的值,对象实例是否是某个给定的值类型的装了箱的值,然后将值从实例中复制出来。然后将值从实例中复制出来。
![Page 77: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/77.jpg)
参照前面关于装箱类的描述,从对象参照前面关于装箱类的描述,从对象 boxbox 到值类型到值类型TT的取消装箱转换相当于执行表达式 的取消装箱转换相当于执行表达式 ((T_Box)box).((T_Box)box).valuevalue 。因此,下面的语句:。因此,下面的语句:
object box = 123;object box = 123;int i = (int)box;int i = (int)box;在概念上相当于:在概念上相当于:object box = new int_Box(123);object box = new int_Box(123);int i = ((int_Box)box).value;int i = ((int_Box)box).value;为使到给定值类型的取消装箱转换在运行时取为使到给定值类型的取消装箱转换在运行时取
得成功,源操作数的值必须是对某个对象的引用,得成功,源操作数的值必须是对某个对象的引用,而该对象先前是通过将该值类型的某个值装箱而创而该对象先前是通过将该值类型的某个值装箱而创建的。如果源操作数为建的。如果源操作数为 nullnull ,则将引发,则将引发 System.NSystem.NullReferenceExceptionullReferenceException 。如果源操作数是对不兼。如果源操作数是对不兼容对象的引用,则将引发容对象的引用,则将引发 System.InvalidCastExceSystem.InvalidCastExceptionption 。。
![Page 78: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/78.jpg)
4.4 4.4 结构和接口结构和接口4.4.1 4.4.1 结构结构结构与类有很多相似之处:结构可以实现接口,并结构与类有很多相似之处:结构可以实现接口,并
且可以具有与类相同的成员类型。然而,结构在几个重要且可以具有与类相同的成员类型。然而,结构在几个重要方面不同于类:结构为值类型而不是引用类型,并且结构方面不同于类:结构为值类型而不是引用类型,并且结构不支持继承。结构的值存储在“在堆栈上”或“内联”。不支持继承。结构的值存储在“在堆栈上”或“内联”。细心的程序员有时可以通过聪明地使用结构来增强性能。细心的程序员有时可以通过聪明地使用结构来增强性能。
声明一个结构,它有三个成员:一个属性、一个方声明一个结构,它有三个成员:一个属性、一个方法和一个私有字段,创建该结构的一个实例,并将其投入法和一个私有字段,创建该结构的一个实例,并将其投入使用:使用:
案例案例:声明一个结构:声明一个结构目标目标:了解声明结构的基本方法:了解声明结构的基本方法步骤步骤::11 、启动、启动 VS.NETVS.NET,新建一个控制台应用程序,名称,新建一个控制台应用程序,名称
填写为“填写为“ SimpleStructTest1”SimpleStructTest1” ,位置设置为“,位置设置为“ c:\CShac:\CSharpSamples\chp4”rpSamples\chp4” 。。
![Page 79: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/79.jpg)
22、在代码设计窗口中编辑、在代码设计窗口中编辑 Class1.csClass1.cs 。其中的代码。其中的代码编写如下:编写如下: using System;using System; namespace SimpleStructTest1namespace SimpleStructTest1 {{
struct SimpleStructstruct SimpleStruct {{
private int xval;private int xval; public int Xpublic int X {{
get get {{
return xval;return xval;}}set set {{
![Page 80: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/80.jpg)
if (value < 100)if (value < 100)xval = value;xval = value;
}}}}public void DisplayX()public void DisplayX(){ Console.WriteLine("The stored value is: {0}", x{ Console.WriteLine("The stored value is: {0}", x
val);val);}}
}}class TestClassclass TestClass{ public static void Main(){ public static void Main()
{ SimpleStruct ss = new SimpleStruct();{ SimpleStruct ss = new SimpleStruct(); ss.X = 5;ss.X = 5; ss.DisplayX();ss.DisplayX();}}
}}}}
![Page 81: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/81.jpg)
33、按、按 Ctrl + F5Ctrl + F5编译并运行该程序,效果如图编译并运行该程序,效果如图 4-44-4所示。所示。
将 将 Point Point 定义为结构而不是类在运行时可以定义为结构而不是类在运行时可以节省很多内存空间。下面的程序创建并初始化一节省很多内存空间。下面的程序创建并初始化一个 个 100 100 点的数组。对于作为类实现的 点的数组。对于作为类实现的 PointPoint ,,出现了 出现了 101 101 个实例对象,因为数组需要一个,它个实例对象,因为数组需要一个,它的 的 100 100 个元素每个都需要一个。个元素每个都需要一个。
图 4-4 程序运行结果
![Page 82: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/82.jpg)
程序清单:程序清单:using System;using System;namespace ConsoleApplication1namespace ConsoleApplication1{ class Point{ class Point
{ public int x, y;{ public int x, y;public Point(int x, int y) public Point(int x, int y) { this.x = x;{ this.x = x;
this.y = y;this.y = y;}}
}}class Testclass Test{ static void Main() { static void Main()
{ Point[] points = new Point[100];{ Point[] points = new Point[100];for (int i = 0; i < 100; i++)for (int i = 0; i < 100; i++)
points[i] = new Point(i, i*i);points[i] = new Point(i, i*i); }}
}}}}
![Page 83: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/83.jpg)
如果将 如果将 Point Point 改为作为结构实现,如:改为作为结构实现,如:struct Pointstruct Point
{{public int x, y;public int x, y;public Point(int x, int y) public Point(int x, int y) {{
this.x = x;this.x = x;this.y = y;this.y = y;
}}}}
![Page 84: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/84.jpg)
则只出现一个实例对象(用于数组的对象)。则只出现一个实例对象(用于数组的对象)。 PoinPoint t 实例在数组中内联分配。此优化可能会被误用。实例在数组中内联分配。此优化可能会被误用。使用结构而不是类还会使应用程序运行得更慢或占使用结构而不是类还会使应用程序运行得更慢或占用更多的内存,因为将结构实例作为值参数传递会用更多的内存,因为将结构实例作为值参数传递会导致创建结构的副本。导致创建结构的副本。案例案例:当向方法传递结构时,将传递该结构的副本,:当向方法传递结构时,将传递该结构的副本,
而传递类实例时,将传递一个引用。而传递类实例时,将传递一个引用。目标目标: 掌握传递结构的方法: 掌握传递结构的方法步骤步骤::11 、启动、启动 VS.NETVS.NET,新建一个控制台应用程序,名称,新建一个控制台应用程序,名称填写为“填写为“ ClasstakerTest”ClasstakerTest” ,位置设置为“,位置设置为“ c:\Cc:\CSharpSamples\chp4”SharpSamples\chp4” 。。22、在代码设计窗口中编辑、在代码设计窗口中编辑 Class1.csClass1.cs 。其中的代码。其中的代码
编写如下:编写如下:
![Page 85: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/85.jpg)
using System;using System;namespace ClasstakerTestnamespace ClasstakerTest
{ class TheClass{ class TheClass{ public int x;{ public int x;}}struct TheStructstruct TheStruct{ public int x;{ public int x;}}class TestClassclass TestClass{ public static void structtaker(TheStruct s){ public static void structtaker(TheStruct s)
{{s.x = 5;s.x = 5;
}}
![Page 86: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/86.jpg)
public static void classtaker(TheClass c)public static void classtaker(TheClass c) { c.x = 5; }{ c.x = 5; }
public static void Main()public static void Main(){ TheStruct a = new TheStruct();{ TheStruct a = new TheStruct();
TheClass b = new TheClass();TheClass b = new TheClass();a.x = 1;a.x = 1;b.x = 1;b.x = 1;structtaker(a);structtaker(a);classtaker(b);classtaker(b);Console.WriteLine("a.x = {0}", a.x);Console.WriteLine("a.x = {0}", a.x);Console.WriteLine("b.x = {0}", b.x);Console.WriteLine("b.x = {0}", b.x);
}}}}
}}
![Page 87: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/87.jpg)
33、按、按 Ctrl + F5Ctrl + F5编译并运行该程序编译并运行该程序本示例的输出表明:当向本示例的输出表明:当向 classtakerclasstaker
方法传递类实例时,只更改了类字段的值。方法传递类实例时,只更改了类字段的值。但是向 但是向 structtakerstructtaker 方法传递结构实例并不方法传递结构实例并不更改结构字段。这是因为向更改结构字段。这是因为向 structtakerstructtaker 方方法传递的是结构的副本,而向法传递的是结构的副本,而向 classtakerclasstaker方法传递的是对类的引用。方法传递的是对类的引用。
结构可以声明构造函数,但它们必须带结构可以声明构造函数,但它们必须带参数。声明结构的默认(无参数)构造函参数。声明结构的默认(无参数)构造函数是错误的。结构成员不能有初始值设定数是错误的。结构成员不能有初始值设定项。总是提供默认构造函数以将结构成员项。总是提供默认构造函数以将结构成员初始化为它们的默认值。初始化为它们的默认值。
![Page 88: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/88.jpg)
使用使用 NewNew运算符创建结构对象时,将创建该结运算符创建结构对象时,将创建该结构对象,并且调用适当的构造函数。与类不同构对象,并且调用适当的构造函数。与类不同的是,结构的实例化可以不使用的是,结构的实例化可以不使用 NewNew运算符。运算符。如果不使用“新建”如果不使用“新建” (new)(new) ,那么在初始化所,那么在初始化所有字段之前,字段将保持未赋值状态,且对象有字段之前,字段将保持未赋值状态,且对象不可用。不可用。
对于结构,不像类那样存在继承。一个对于结构,不像类那样存在继承。一个结构不能从另一个结构或类继承,而且不结构不能从另一个结构或类继承,而且不能作为一个类的基。但是,结构从基类对能作为一个类的基。但是,结构从基类对象继承。结构可实现接口,而且实现方式象继承。结构可实现接口,而且实现方式与类实现接口的方式完全相同。以下是结与类实现接口的方式完全相同。以下是结构实现接口的代码片段:构实现接口的代码片段:
![Page 89: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/89.jpg)
interface IImageinterface IImage{{ void Paint(); void Paint();}}struct Picture : IImagestruct Picture : IImage{{ public void Paint() public void Paint() { { // painting code goes here // painting code goes here } } private int x, y, z; // other struct members private int x, y, z; // other struct members}}结构使用简单,并且有时证明很有用。 结构使用简单,并且有时证明很有用。
![Page 90: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/90.jpg)
但要牢记:结构在堆栈中创建,并且您不是处但要牢记:结构在堆栈中创建,并且您不是处理对结构的引用,而是直接处理结构。每当需要一理对结构的引用,而是直接处理结构。每当需要一种将经常使用的类型,而且大多数情况下该类型只种将经常使用的类型,而且大多数情况下该类型只是一些数据时,结构可能是最佳选择。是一些数据时,结构可能是最佳选择。
![Page 91: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/91.jpg)
4.4.2 4.4.2 接口接口4.4.2.1 4.4.2.1 什么是接口什么是接口
接口(接口( interfaceinterface )用来定义一种程序)用来定义一种程序的协定。实现接口的类或者结构要与接口的协定。实现接口的类或者结构要与接口的定义严格一致。有了这个协定,就可以的定义严格一致。有了这个协定,就可以抛开编程语言的限制(理论上)。接口可抛开编程语言的限制(理论上)。接口可以从多个基接口继承,而类或结构可以实以从多个基接口继承,而类或结构可以实现多个接口。接口可以包含方法、属性、现多个接口。接口可以包含方法、属性、事件和索引器。接口本身不提供它所定义事件和索引器。接口本身不提供它所定义的成员的实现。接口只指定实现该接口的的成员的实现。接口只指定实现该接口的类或接口必须提供的成员。类或接口必须提供的成员。
![Page 92: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/92.jpg)
接口好比一种模版,这种模版定义了对象必须实现接口好比一种模版,这种模版定义了对象必须实现的方法,其目的就是让这些方法可以作为接口实例的方法,其目的就是让这些方法可以作为接口实例被引用。接口不能被实例化。类可以实现多个接口被引用。接口不能被实例化。类可以实现多个接口并且通过这些实现的接口被索引。接口变量只能索并且通过这些实现的接口被索引。接口变量只能索引实现该接口的类的实例。例子:引实现该接口的类的实例。例子:
interface ImyExampleinterface ImyExample{ string this[int index] { get ; set ; } { string this[int index] { get ; set ; } event EventHandler Even ; event EventHandler Even ; void Find(int value) ; void Find(int value) ; string Point { get ; set ; } string Point { get ; set ; } }}public delegate void EventHandler(object sepublic delegate void EventHandler(object se
nder, Event e) ; nder, Event e) ;
![Page 93: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/93.jpg)
上面例子中的接口包含一个索引上面例子中的接口包含一个索引 thisthis 、一个事件、一个事件 EvEvenen 、一个方法、一个方法 FindFind 和一个属性和一个属性 PointPoint 。。接口可以支持多重继承。就像在下例中,接口“接口可以支持多重继承。就像在下例中,接口“ IcoIcomboBox”mboBox” 同时从“同时从“ ItextBox”ItextBox” 和“和“ IlistBox”IlistBox” 继继承。承。interface IControl interface IControl
{ void Paint( ) ; { void Paint( ) ; }}interface ITextBox: IControl interface ITextBox: IControl { void SetText(string text) ; { void SetText(string text) ; } } interface IListBox: IControl interface IListBox: IControl { void SetItems(string[] items) ; { void SetItems(string[] items) ; } } interface IComboBox: ITextBox, IListBox { } interface IComboBox: ITextBox, IListBox { }
![Page 94: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/94.jpg)
类和结构可以多重实例化接口。就像在下例中,类类和结构可以多重实例化接口。就像在下例中,类““ EditBox”EditBox” 继承了类“继承了类“ Control”Control” ,同时从“,同时从“ IdIdataBound”ataBound” 和“和“ Icontrol”Icontrol” 继承。继承。
interface IDataBound interface IDataBound {{ void Bind(Binder b) ; void Bind(Binder b) ; } } public class EditBox: Control, IControl, IDatapublic class EditBox: Control, IControl, IData
Bound Bound {{ public void Paint( ) ; public void Paint( ) ; public void Bind(Binder b) {...} public void Bind(Binder b) {...} } }
![Page 95: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/95.jpg)
在上面的代码中,“在上面的代码中,“ Paint”Paint” 方法从“方法从“ Icontrol”Icontrol”接口而来;“接口而来;“ Bind”Bind” 方法从“方法从“ IdataBound”IdataBound” 接口接口而来,都以“而来,都以“ public”public” 的身份在“的身份在“ EditBox”EditBox” 类中类中实现。实现。说明:说明:11 、、 C#C# 中的接口是独立于类来定义的。这与 中的接口是独立于类来定义的。这与 C++C++ 模型是对模型是对
立的,在 立的,在 C++C++ 中接口实际上就是抽象基类。中接口实际上就是抽象基类。22、接口和类都可以继承多个接口。、接口和类都可以继承多个接口。33、而类可以继承一个基类,接口根本不能继承类。这种模、而类可以继承一个基类,接口根本不能继承类。这种模
型避免了 型避免了 C++C++ 的多继承问题,的多继承问题, C++C++ 中不同基类中的实现中不同基类中的实现可能出现冲突。因此也不再需要诸如虚拟继承和显式作用可能出现冲突。因此也不再需要诸如虚拟继承和显式作用域这类复杂机制。域这类复杂机制。 C#C# 的简化接口模型有助于加快应用程的简化接口模型有助于加快应用程序的开发。序的开发。
44 、一个接口定义一个只有抽象成员的引用类型。、一个接口定义一个只有抽象成员的引用类型。 C#C# 中一中一个接口实际所做的,仅仅只存在着方法标志,但根本就没个接口实际所做的,仅仅只存在着方法标志,但根本就没有执行代码。这就暗示了不能实例化一个接口,只能实例有执行代码。这就暗示了不能实例化一个接口,只能实例化一个派生自该接口的对象。化一个派生自该接口的对象。
![Page 96: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/96.jpg)
55、接口可以定义方法、属性和索引。所以,对比、接口可以定义方法、属性和索引。所以,对比一个类,接口的特殊性是:当定义一个类时,可以一个类,接口的特殊性是:当定义一个类时,可以派生自多重接口,而你只能可以从仅有的一个类派派生自多重接口,而你只能可以从仅有的一个类派生。生。◆◆接口与组件接口与组件 接口描述了组件对外提供的服务。在组件和组件之间、接口描述了组件对外提供的服务。在组件和组件之间、组件和客户之间都通过接口进行交互。因此组件一旦发布,组件和客户之间都通过接口进行交互。因此组件一旦发布,它只能通过预先定义的接口来提供合理的、一致的服务。它只能通过预先定义的接口来提供合理的、一致的服务。这种接口定义之间的稳定性使客户应用开发者能够构造出这种接口定义之间的稳定性使客户应用开发者能够构造出坚固的应用。一个组件可以实现多个组件接口,而一个特坚固的应用。一个组件可以实现多个组件接口,而一个特定的组件接口也可以被多个组件来实现。定的组件接口也可以被多个组件来实现。
组件接口必须是能够自我描述的。这意味着组件接口组件接口必须是能够自我描述的。这意味着组件接口应该不依赖于具体的实现,将实现和接口分离彻底消除了应该不依赖于具体的实现,将实现和接口分离彻底消除了接口的使用者和接口的实现者之间的耦合关系,增强了信接口的使用者和接口的实现者之间的耦合关系,增强了信息的封装程度。同时这也要求组件接口必须使用一种与组息的封装程度。同时这也要求组件接口必须使用一种与组件实现无关的语言。目前组件接口的描述标准是件实现无关的语言。目前组件接口的描述标准是 IDLIDL 语言。语言。
![Page 97: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/97.jpg)
由于接口是组件之间的协议,因此组件的接口一旦被发布,由于接口是组件之间的协议,因此组件的接口一旦被发布,组件生产者就应该尽可能地保持接口不变,任何对接口语法组件生产者就应该尽可能地保持接口不变,任何对接口语法或语义上的改变,都有可能造成现有组件与客户之间的联系或语义上的改变,都有可能造成现有组件与客户之间的联系
遭到破坏。遭到破坏。 每个组件都是自主的,有其独特的功能,只能通过接口每个组件都是自主的,有其独特的功能,只能通过接口与外界通信。当一个组件需要提供新的服务时,可以通过与外界通信。当一个组件需要提供新的服务时,可以通过增加新的接口来实现。不会影响原接口已存在的客户。而增加新的接口来实现。不会影响原接口已存在的客户。而新的客户可以重新选择新的接口来获得服务。新的客户可以重新选择新的接口来获得服务。
◆◆组件化程序设计组件化程序设计 组件化程序设计方法继承并发展了面向对象的程序设计组件化程序设计方法继承并发展了面向对象的程序设计
方法。它把对象技术应用于系统设计,对面向对象的程序方法。它把对象技术应用于系统设计,对面向对象的程序设计的实现过程作了进一步的抽象。我们可以把组件化程设计的实现过程作了进一步的抽象。我们可以把组件化程序设计方法用作构造系统的体系结构层次的方法,并且可序设计方法用作构造系统的体系结构层次的方法,并且可以使用面向对象的方法很方便地实现组件。以使用面向对象的方法很方便地实现组件。
组件化程序设计强调真正的软件可重用性和高度的互操组件化程序设计强调真正的软件可重用性和高度的互操作性。它侧重于组件的产生和装配,这两方面一起构成了作性。它侧重于组件的产生和装配,这两方面一起构成了组件化程序设计的核心。 组件化程序设计的核心。
![Page 98: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/98.jpg)
组件的产生过程不仅仅是应用系统的需求,组件市组件的产生过程不仅仅是应用系统的需求,组件市场本身也推动了组件的发展,促进了软件厂商的交场本身也推动了组件的发展,促进了软件厂商的交流与合作。组件的装配使得软件产品可以采用类似流与合作。组件的装配使得软件产品可以采用类似于搭积木的方法快速地建立起来,不仅可以缩短软于搭积木的方法快速地建立起来,不仅可以缩短软件产品的开发周期,同时也提高了系统的稳定性和件产品的开发周期,同时也提高了系统的稳定性和可靠性。可靠性。组件程序设计的方法有以下几个方面的特点:组件程序设计的方法有以下几个方面的特点:11 、编程语言和开发环境的独立性;、编程语言和开发环境的独立性;22、组件位置的透明性;、组件位置的透明性;33、组件的进程透明性;、组件的进程透明性;44 、可扩充性;、可扩充性;55、可重用性;、可重用性;66、具有强有力的基础设施;、具有强有力的基础设施;77、系统一级的公共服务。、系统一级的公共服务。
![Page 99: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/99.jpg)
C#C# 语言由于其许多优点,十分适用于组件编程。语言由于其许多优点,十分适用于组件编程。但这并不是说但这并不是说 C#C# 是一门组件编程语言,也不是说是一门组件编程语言,也不是说 CC##提供了组件编程的工具。我们已经多次指出,组提供了组件编程的工具。我们已经多次指出,组件应该具有与编程语言无关的特性。请读者记住这件应该具有与编程语言无关的特性。请读者记住这一点:组件模型是一种规范,不管采用何种程序语一点:组件模型是一种规范,不管采用何种程序语言设计组件,都必须遵守这一规范。比如组装计算言设计组件,都必须遵守这一规范。比如组装计算机的例子,只要各个厂商为我们提供的配件规格、机的例子,只要各个厂商为我们提供的配件规格、接口符合统一的标准,这些配件组合起来就能协同接口符合统一的标准,这些配件组合起来就能协同工作,组件编程也是一样。我们只是说,利用工作,组件编程也是一样。我们只是说,利用 C#C#语言进行组件编程将会给我们带来更大的方便。语言进行组件编程将会给我们带来更大的方便。知道了什么是接口,接下来就是怎样定义接口。知道了什么是接口,接下来就是怎样定义接口。
![Page 100: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/100.jpg)
4.4.2.2 4.4.2.2 定义接口定义接口
从技术上讲,接口是一组包含了函数型从技术上讲,接口是一组包含了函数型方法的数据结构。通过这组数据结构,客方法的数据结构。通过这组数据结构,客户代码可以调用组件对象的功能。户代码可以调用组件对象的功能。
![Page 101: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/101.jpg)
定义接口的一般形式为:定义接口的一般形式为: [ [ 接口修饰符 接口修饰符 ] interface ] interface 接口名 接口名 [: [: 基类接口名基类接口名 ]] { { // // 接口的成员接口的成员 ;; } } 其中接口的修饰符可以是其中接口的修饰符可以是 newnew 、、 publicpublic 、、 prpr
otectedotected 、、 internalinternal 和和 privateprivate 。。 NewNew修饰符是修饰符是在嵌套接口中唯一允许存在的修饰符,它说明用在嵌套接口中唯一允许存在的修饰符,它说明用相同的名称隐藏一个继承的成员。相同的名称隐藏一个继承的成员。 PublicPublic 、、 protprotecedeced 、、 internalinternal 和和 pricatepricate修饰符控制接口的访修饰符控制接口的访问能力。问能力。
接口这个概念在接口这个概念在 C#C# 和和 JavaJava 中非常相似。接口中非常相似。接口的关键词是的关键词是 interfaceinterface ,一个接口可以扩展一个或,一个接口可以扩展一个或者多个其他接口。按照惯例,接口的名字以大写者多个其他接口。按照惯例,接口的名字以大写字母“字母“ I”I”开头。下面的代码是开头。下面的代码是 C#C# 接口的一个例接口的一个例子,它与子,它与 JavaJava 中的接口完全一样:中的接口完全一样:
![Page 102: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/102.jpg)
interface IShape interface IShape {{ void Draw ( ) ; void Draw ( ) ;}}
如果你从两个或者两个以上的接口派生,父接口如果你从两个或者两个以上的接口派生,父接口的名字列表用逗号分隔,如下面的代码所示:的名字列表用逗号分隔,如下面的代码所示:
interface INewInterface: IParent1, IParent2 { } interface INewInterface: IParent1, IParent2 { } 然而,与然而,与 JavaJava 不同,不同, C#C# 中的接口不能包含域中的接口不能包含域
(( FieldField )。另外还要注意,在)。另外还要注意,在 C#C# 中,接口内的所中,接口内的所有方法默认都是公用方法。在有方法默认都是公用方法。在 JavaJava 中,方法定义可中,方法定义可以带有以带有 publicpublic修饰符(即使这并非必要),但在修饰符(即使这并非必要),但在 C#C#中,显式为接口的方法指定中,显式为接口的方法指定 publicpublic修饰符是非法的。修饰符是非法的。例如,下面的例如,下面的 C#C# 接口将产生一个编译错误。接口将产生一个编译错误。
![Page 103: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/103.jpg)
◆◆基接口基接口 一个接口可以从零或多个接口继承,那些被称为 一个接口可以从零或多个接口继承,那些被称为这个接口的显式基接口。当一个接口有比零多的显这个接口的显式基接口。当一个接口有比零多的显式基接口时,那么在接口的定义中的形式为,接口式基接口时,那么在接口的定义中的形式为,接口标识符后面跟着由一个冒号“标识符后面跟着由一个冒号“ :”:” 和一个用逗号和一个用逗号““ ,”,” 分开的基接口标识符列表。分开的基接口标识符列表。◆接口基◆接口基接口类型列表说明:接口类型列表说明:11 、一个接口的显式基接口必须至少同接口本身一样可访问。、一个接口的显式基接口必须至少同接口本身一样可访问。例如,在一个公共接口的基接口中指定一个私有或内部的例如,在一个公共接口的基接口中指定一个私有或内部的接口是错误的。接口是错误的。
22、一个接口直接或间接地从它自己继承是错误的。、一个接口直接或间接地从它自己继承是错误的。33、接口的基接口都是显式基接口,并且是它们的基接口。、接口的基接口都是显式基接口,并且是它们的基接口。换句话说,基接口的集合完全由显式基接口和它们的显式换句话说,基接口的集合完全由显式基接口和它们的显式基接口等等组成。基接口等等组成。
![Page 104: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/104.jpg)
44 、一个接口继承它的基接口的所有成员。、一个接口继承它的基接口的所有成员。55、一个实现了接口的类或结构也隐含地实现了所、一个实现了接口的类或结构也隐含地实现了所有接口的基接口。有接口的基接口。定义接口主要是定义接口成员,我们在下面介绍接定义接口主要是定义接口成员,我们在下面介绍接口成员。口成员。
![Page 105: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/105.jpg)
4.4.2.3 4.4.2.3 定义接口成员定义接口成员
接口可以包含一个和多个成员,这些成员可以接口可以包含一个和多个成员,这些成员可以是方法、属性、索引指示器和事件,但不能是常是方法、属性、索引指示器和事件,但不能是常量、域、操作符、构造函数或析构函数,而且不量、域、操作符、构造函数或析构函数,而且不能包含任何静态成员。接口定义创建新的定义空能包含任何静态成员。接口定义创建新的定义空间,并且接口定义直 接包含的接口成员定义将新间,并且接口定义直 接包含的接口成员定义将新成员引入该定义空间。成员引入该定义空间。
说明:说明:11 、接口的成员是从基接口继承的成员和由接口本、接口的成员是从基接口继承的成员和由接口本
身定义的成员。身定义的成员。
![Page 106: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/106.jpg)
22、接口定义可以定义零个或多个成员。接口的成、接口定义可以定义零个或多个成员。接口的成员必须是方法、属性、事件或索引器。接口不能包员必须是方法、属性、事件或索引器。接口不能包含常数、字段、运算符、实例构造函数、析构函数含常数、字段、运算符、实例构造函数、析构函数或类型,也不能包含任何种类的静态成员。或类型,也不能包含任何种类的静态成员。33、定义一个接口,该接口对于每种可能种类的成员都包含、定义一个接口,该接口对于每种可能种类的成员都包含
一个:方法、属性、事件和索引器。一个:方法、属性、事件和索引器。44 、接口成员默认访问方式是、接口成员默认访问方式是 publicpublic 。接口成员定义不能包。接口成员定义不能包含任何修饰符,比如成员定义前不能加含任何修饰符,比如成员定义前不能加 abstractabstract ,, publipublicc ,, protectedprotected ,, internalinternal ,, privateprivate ,, virtualvirtual ,, overrioverride de 或或 static static 修饰符。修饰符。
55、接口的成员之间不能相互同名。继承而来的成员不用再、接口的成员之间不能相互同名。继承而来的成员不用再定义,但接口可以定义与继承而来的成员同名的成员,这定义,但接口可以定义与继承而来的成员同名的成员,这时我们说接口成员覆盖了继承而来的成员,这不会导致错时我们说接口成员覆盖了继承而来的成员,这不会导致错误,但编译器会给出一个警告。关闭警告提示的方式是在误,但编译器会给出一个警告。关闭警告提示的方式是在成员定义前加上一个成员定义前加上一个 newnew关键字。但如果没有覆盖父接关键字。但如果没有覆盖父接口中的成员,使用口中的成员,使用 new new 关键字会导致编译器发出警告。关键字会导致编译器发出警告。
![Page 107: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/107.jpg)
66、方法的名称必须与同一接口中定义的所有属性和事件、方法的名称必须与同一接口中定义的所有属性和事件的名称不同。此外,方法的签名必须与同一接口中定义的的名称不同。此外,方法的签名必须与同一接口中定义的所有其他方法的签名不同。所有其他方法的签名不同。
77、属性或事件的名称必须与同一接口中定义的所有其他成、属性或事件的名称必须与同一接口中定义的所有其他成员的名称不同。员的名称不同。
88、一个索引器的签名必须区别于在同一接口中定义的其他、一个索引器的签名必须区别于在同一接口中定义的其他所有索引器的签名。所有索引器的签名。
99、接口方法声明中的属性(、接口方法声明中的属性( attributesattributes )) , , 返回类型(返回类型( rereturn-typeturn-type )) , , 标识符(标识符( identifieridentifier )) , , 和形式参数列表和形式参数列表(( formal-parameter-lisformal-parameter-lis )与一个类的方法声明中的那些)与一个类的方法声明中的那些有相同的意义。一个接口方法声明不允许指定一个方法主有相同的意义。一个接口方法声明不允许指定一个方法主体,而声明通常用一个分号结束。体,而声明通常用一个分号结束。
1010、接口属性声明的访问符与类属性声明的访问符相对应,、接口属性声明的访问符与类属性声明的访问符相对应,除了访问符主体通常必须用分号。因此,无论属性是读写、除了访问符主体通常必须用分号。因此,无论属性是读写、只读或只写,访问符都完全确定。只读或只写,访问符都完全确定。
![Page 108: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/108.jpg)
1111 、接口索引声明中的属性(、接口索引声明中的属性( attributesattributes )) , , 类型类型(( typetype )) , , 和形式参数列表 (和形式参数列表 ( formal-parameteformal-parameter-listr-list )与类的索引声明的那些有相同的意义。)与类的索引声明的那些有相同的意义。 ◆◆接口成员的全权名接口成员的全权名 使用接口成员也可采用全权名(使用接口成员也可采用全权名( fully qualified nafully qualified na
meme )。接口的全权名称是这样构成的。接口名加)。接口的全权名称是这样构成的。接口名加小圆点“小圆点“ .” .” 再跟成员名比如对于下面两个接口:再跟成员名比如对于下面两个接口:
interface IControl interface IControl { void Paint( ) ;{ void Paint( ) ;}}interface ITextBox: IControl interface ITextBox: IControl {{ void GetText(string text) ;void GetText(string text) ;}}
![Page 109: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/109.jpg)
其中其中 Paint Paint 的全权名是的全权名是 IControl.PaintIControl.Paint ,, GetTextGetText的全权名是的全权名是 ITextBox. GetTextITextBox. GetText 。当然,全权名中。当然,全权名中的成员名称必须是在接口中已经定义过的,比如使的成员名称必须是在接口中已经定义过的,比如使用用 ITextBox.Paint.ITextBox.Paint.就是不合理的。就是不合理的。如果接口是名字空间的成员,全权名还必须包含名字空间的如果接口是名字空间的成员,全权名还必须包含名字空间的名称。名称。
namespace Systemnamespace System{ public interface IdataTable{ public interface IdataTable { object Clone( ) ;{ object Clone( ) ; }}}} 那么那么 CloneClone 方法的全权名是方法的全权名是 System. IDataTable.ClonSystem. IDataTable.Clon
ee 。。 定义好了接口,接下来就是怎样访问接口,下面对访定义好了接口,接下来就是怎样访问接口,下面对访
问接口进行介绍。问接口进行介绍。
![Page 110: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/110.jpg)
4.4.2.44.4.2.4 访问接口访问接口
◆◆对接口成员的访问对接口成员的访问 对接口方法的调用和采用索引指示器访问的规对接口方法的调用和采用索引指示器访问的规
则与类中的情况也是相同的。如果底层成员的命则与类中的情况也是相同的。如果底层成员的命名与继承而来的高层成员一致,那么底层成员将名与继承而来的高层成员一致,那么底层成员将覆盖同名的高层成员。但由于接口支持多继承,覆盖同名的高层成员。但由于接口支持多继承,在多继承中,如果两个父接口含有同名的成员,在多继承中,如果两个父接口含有同名的成员,这就产生了二义性(这也正是这就产生了二义性(这也正是 C#C# 中取消了类的多中取消了类的多继承机制的原因之一),这时需要进行显式的定继承机制的原因之一),这时需要进行显式的定义。义。
![Page 111: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/111.jpg)
◆ ◆类对接口的实现类对接口的实现
前面我们已经说过,接口定义不包括方前面我们已经说过,接口定义不包括方法的实现部分。接口可以通过类或结构来法的实现部分。接口可以通过类或结构来实现。我们主要讲述通过类来实现接口。实现。我们主要讲述通过类来实现接口。用类来实现接口时,接口的名称必须包含用类来实现接口时,接口的名称必须包含在类定义中的基类列表中。在类定义中的基类列表中。
![Page 112: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/112.jpg)
4.4.2.54.4.2.5 实现接口实现接口
11 、显式实现接口成员、显式实现接口成员 为了实现接口,类可以定义显式接口成员执行体为了实现接口,类可以定义显式接口成员执行体
(( Explicit interface member implementationExplicit interface member implementationss )。)。
![Page 113: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/113.jpg)
显式接口成员执行体可以是一个方法、一个属性、显式接口成员执行体可以是一个方法、一个属性、一个事件或者是一个索引指示器的定义,定义与该一个事件或者是一个索引指示器的定义,定义与该成员对应的全权名应保持一致。成员对应的全权名应保持一致。
using System ;using System ;interface ICloneable interface ICloneable { object Clone( ) ; }{ object Clone( ) ; }interface IComparable interface IComparable { int CompareTo(object other) ;{ int CompareTo(object other) ;}}class ListEntry: ICloneable, IComparable class ListEntry: ICloneable, IComparable { object ICloneable.Clone( ) {…}{ object ICloneable.Clone( ) {…} int IComparable.CompareTo(object other) {…}int IComparable.CompareTo(object other) {…}}}
![Page 114: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/114.jpg)
上面的代码中上面的代码中 ICloneable.Clone ICloneable.Clone 和和 IComparable.IComparable.CompareTo CompareTo 就是显式接口成员执行体。 说明:就是显式接口成员执行体。 说明:
(( 11 )、不能在方法调用、属性访问以及索引指示)、不能在方法调用、属性访问以及索引指示器访问中通过全权名访问显式接口成员执行体。器访问中通过全权名访问显式接口成员执行体。事实上,显式接口成员执行体只能通过接口的实事实上,显式接口成员执行体只能通过接口的实例,仅仅引用接口的成员名称来访问。例,仅仅引用接口的成员名称来访问。
(( 22)、显式接口成员执行体不能使用任何访问限)、显式接口成员执行体不能使用任何访问限制符,也不能加上制符,也不能加上 abstract, virtual, overrideabstract, virtual, override或或static static 修饰符。修饰符。
(( 33)、显式接口成员执行体和其他成员有着不同)、显式接口成员执行体和其他成员有着不同的访问方式。因为不能在方法调用、属性访问以的访问方式。因为不能在方法调用、属性访问以及索引指示器访问中通过全权名访问,显式接口及索引指示器访问中通过全权名访问,显式接口成员执行体在某种意义上是私有的。但它们又可成员执行体在某种意义上是私有的。但它们又可以通过接口的实例访问,也具有一定的公有性质。以通过接口的实例访问,也具有一定的公有性质。
![Page 115: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/115.jpg)
(( 44 )、只有类在定义时,把接口名写在了基类列)、只有类在定义时,把接口名写在了基类列表中,而且类中定义的全权名、类型和返回类型都表中,而且类中定义的全权名、类型和返回类型都与显式接口成员执行体完全一致时,显式接口成员与显式接口成员执行体完全一致时,显式接口成员执行体才是有效的,例如:执行体才是有效的,例如:
class Shape: ICloneable class Shape: ICloneable {{ object ICloneable.Clone( ) {…}object ICloneable.Clone( ) {…} int IComparable.CompareTo(object int IComparable.CompareTo(object
other) {…}other) {…}}}
![Page 116: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/116.jpg)
使用显式接口成员执行体通常有两个目的:使用显式接口成员执行体通常有两个目的:
(( 11 )、因为显式接口成员执行体不能通过类的实)、因为显式接口成员执行体不能通过类的实例进行访问,这就可以从公有接口中把接口的实例进行访问,这就可以从公有接口中把接口的实现部分单独分离开。如果一个类只在内部使用该现部分单独分离开。如果一个类只在内部使用该接口,而类的使用者不会直接使用到该接口,这接口,而类的使用者不会直接使用到该接口,这种显式接口成员执行体就可以起到作用。种显式接口成员执行体就可以起到作用。
(( 22)、显式接口成员执行体避免了接口成员之间)、显式接口成员执行体避免了接口成员之间因为同名而发生混淆。如果一个类希望对名称和因为同名而发生混淆。如果一个类希望对名称和返回类型相同的接口成员采用不同的实现方式,返回类型相同的接口成员采用不同的实现方式,这就必须要使用到显式接口成员执行体。如果没这就必须要使用到显式接口成员执行体。如果没有显式接口成员执行体,那么对于名称和返回类有显式接口成员执行体,那么对于名称和返回类型不同的接口成员,类也无法进行实现。型不同的接口成员,类也无法进行实现。
![Page 117: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/117.jpg)
22 、继承接口实现、继承接口实现 接口具有不变性,但这并不意味着接口不再发接口具有不变性,但这并不意味着接口不再发展。类似于类的继承性,接口也可以继承和发展。展。类似于类的继承性,接口也可以继承和发展。
注意:接口继承和类继承不同,首先,类继承注意:接口继承和类继承不同,首先,类继承不仅是说明继承,而且也是实现继承;而接口继不仅是说明继承,而且也是实现继承;而接口继承只是说明继承。也就是说,派生类可以继承基承只是说明继承。也就是说,派生类可以继承基类的方法实现,而派生的接口只继承了父接口的类的方法实现,而派生的接口只继承了父接口的成员方法说明,而没有继承父接口的实现,其次,成员方法说明,而没有继承父接口的实现,其次,C#C# 中类继承只允许单继承,但是接口继承允许多中类继承只允许单继承,但是接口继承允许多继承,一个子接口可以有多个父接口。继承,一个子接口可以有多个父接口。
![Page 118: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/118.jpg)
接口可以从零或多个接口中继承。从多个接口中继接口可以从零或多个接口中继承。从多个接口中继承时,用“承时,用“ :”:”后跟被继承的接口名字,多个接口后跟被继承的接口名字,多个接口名之间用“名之间用“ ,”,” 分割。被继承的接口应该是可以访分割。被继承的接口应该是可以访问得到的,比如从问得到的,比如从 private private 类型或类型或 internal internal 类型类型的接口中继承就是不允许的。接口不允许直接或间的接口中继承就是不允许的。接口不允许直接或间接地从自身继承。和类的继承相似,接口的继承也接地从自身继承。和类的继承相似,接口的继承也形成接口之间的层次结构。形成接口之间的层次结构。33、重新实现接口、重新实现接口 我们已经介绍过,派生类可以对基类中已经定义的成我们已经介绍过,派生类可以对基类中已经定义的成
员方法进行重载。类似的概念引入到类对接口的实现中来,员方法进行重载。类似的概念引入到类对接口的实现中来,叫做接口的重实现(叫做接口的重实现( re-implementationre-implementation )。继承了接)。继承了接口实现的类可以对接口进行重实现。这个接口要求是在类口实现的类可以对接口进行重实现。这个接口要求是在类定义的基类列表中出现过的。对接口的重实现也必须严格定义的基类列表中出现过的。对接口的重实现也必须严格地遵守首次实现接口的规则,派生的接口映射不会对为接地遵守首次实现接口的规则,派生的接口映射不会对为接口的重实现所建立的接口映射产生任何影响。口的重实现所建立的接口映射产生任何影响。
![Page 119: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/119.jpg)
下面的代码给出了接口重实现的例子:下面的代码给出了接口重实现的例子:interface IControl interface IControl { void Paint( ) ;{ void Paint( ) ; class Control: Icontrolclass Control: Icontrol void IControl.Paint( ) {…}void IControl.Paint( ) {…} class MyControl: Control, Icontrolclass MyControl: Control, Icontrol public void Paint( ) {}public void Paint( ) {}}}实际上就是:实际上就是: ControlControl把把 IControl.PaintIControl.Paint映射映射
到了到了 Control.IControl.PaintControl.IControl.Paint上,但这并不影响上,但这并不影响在在 MyControlMyControl 中的重实现。在中的重实现。在 MyControlMyControl 中的重中的重实现中,实现中, IControl.PaintIControl.Paint被映射到被映射到 MyControl.PaMyControl.Paint int 之上。之上。
![Page 120: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/120.jpg)
在接口的重实现时,继承而来的公有成员定义在接口的重实现时,继承而来的公有成员定义和继承而来的显式接口成员的定义参与到接口映射和继承而来的显式接口成员的定义参与到接口映射的过程。的过程。
using System ;using System ;interface IMethods interface IMethods { void F( ) ;{ void F( ) ; void G( ) ;void G( ) ; void H( ) ;void H( ) ; void I( ) ;void I( ) ;}}class Base: IMethods class Base: IMethods { void IMethods.F( ) { }{ void IMethods.F( ) { } void IMethods.G( ) { }void IMethods.G( ) { } public void H( ) { }public void H( ) { } public void I( ) { }public void I( ) { }}}
![Page 121: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/121.jpg)
class Derived: Base, Imethodsclass Derived: Base, Imethods{{ public void F( ) { } public void F( ) { } void IMethods.H( ) { } void IMethods.H( ) { }}} 这里,接口这里,接口 IMethodsIMethods 在在 DerivedDerived 中的中的
实现把接口方法映射到了实现把接口方法映射到了 Derived.F,Base.IDerived.F,Base.IMethods.G, Derived.IMethods.H, Methods.G, Derived.IMethods.H, 还有还有 BBase.Iase.I。前面我们说过,类在实现一个接口。前面我们说过,类在实现一个接口时,同时隐式地实现了该接口的所有父接时,同时隐式地实现了该接口的所有父接口。同样,类在重实现一个接口时同时,口。同样,类在重实现一个接口时同时,隐式地重实现了该接口的所有父接口。隐式地重实现了该接口的所有父接口。
![Page 122: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/122.jpg)
using System ;using System ;interface IBaseinterface IBase
{ void F( ) ;{ void F( ) ;} interface IDerived: IBase } interface IDerived: IBase { void G( ) ;{ void G( ) ;} class C: IDerived } class C: IDerived { void IBase.F( ) { void IBase.F( ) { //{ // 对对 F F 进行实现的代码… 进行实现的代码… }} void IDerived.G( ) void IDerived.G( ) { //{ // 对对 G G 进行实现的代码… 进行实现的代码… }}}}class D: C, IDerived class D: C, IDerived { public void F( ) { public void F( ) { //{ // 对对 F F 进行实现的代码… 进行实现的代码… }} public void G( ) public void G( ) { //{ // 对对 G G 进行实现的代码… 进行实现的代码… }}}}
![Page 123: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/123.jpg)
这里,对这里,对 IDerivedIDerived 的重实现也同样实现了对的重实现也同样实现了对 IBaseIBase的重实现,把的重实现,把 IBase.F IBase.F 映射到了映射到了 D.FD.F。。44 、映射接口、映射接口 类必须为在基类表中列出的所有接口的成员提类必须为在基类表中列出的所有接口的成员提供具体的实现。在类中定位接口成员的实现称之供具体的实现。在类中定位接口成员的实现称之为接口映射(为接口映射( interface mapping interface mapping )。)。
映射,数学上表示一一对应的函数关系。接口映射,数学上表示一一对应的函数关系。接口映射的含义也是一样,接口通过类来实现,那么映射的含义也是一样,接口通过类来实现,那么对于在接口中定义的每一个成员,都应该对应着对于在接口中定义的每一个成员,都应该对应着类的一个成员来为它提供具体的实现。类的一个成员来为它提供具体的实现。
类的成员及其所映射的接口成员之间必须满足类的成员及其所映射的接口成员之间必须满足下列条件:下列条件:
(( 11 )、如果)、如果 AA 和和 BB 都是成员方法,那么都是成员方法,那么 AA 和和 BB 的的名称、类型、形参表(包括参数个数和每一个参名称、类型、形参表(包括参数个数和每一个参数的类型)都应该是一致的。数的类型)都应该是一致的。
![Page 124: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/124.jpg)
(( 22)、如果)、如果 AA 和和 BB 都是属性,那么都是属性,那么 AA 和和 BB 的名称、的名称、类型应当一致,而且类型应当一致,而且 AA 和和 BB 的访问器也是类似的。的访问器也是类似的。但如果但如果 AA 不是显式接口成员执行体,不是显式接口成员执行体, AA允许增加自允许增加自己的访问器。己的访问器。(( 33)、如果)、如果 AA 和和 BB 都是时间那么都是时间那么 AA 和和 BB 的名称、的名称、类型应当一致。类型应当一致。(( 44 )、如果)、如果 AA 和和 BB 都是索引指示器,那么都是索引指示器,那么 AA 和和 BB的类型、形参表(包括参数个数和每一个参数的类的类型、形参表(包括参数个数和每一个参数的类型)应当一致。而且型)应当一致。而且 AA 和和 BB 的访问器也是类似的。的访问器也是类似的。但如果但如果 AA 不是显式接口成员执行体,不是显式接口成员执行体, AA允许增加自允许增加自己的访问器。己的访问器。
那么,对于一个接口成员,怎样确定由哪一个那么,对于一个接口成员,怎样确定由哪一个类的成员来实现呢?即一个接口成员映射的是哪一类的成员来实现呢?即一个接口成员映射的是哪一个类的成员? 个类的成员?
![Page 125: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/125.jpg)
在这里,我们叙述一下接口映射的过程。假设类在这里,我们叙述一下接口映射的过程。假设类CC 实现了一个接口实现了一个接口 IInterfaceIInterface ,, MemberMember 是接口是接口 IInIInterfaceterface 中的一个成员,在定位由谁来实现接口成员中的一个成员,在定位由谁来实现接口成员MemberMember ,即,即 MemberMember 的映射过程是这样的:的映射过程是这样的: (( 11 )、如果)、如果 CC 中存在着一个显式接口成员执行体,中存在着一个显式接口成员执行体,该执行体与接口该执行体与接口 IInterface IInterface 及其成员及其成员 MemberMember 相相对应,则由它来实现对应,则由它来实现 Member Member 成员。成员。
(( 22)、如果条件()、如果条件( 11 )不满足,且)不满足,且 CC 中存在着一中存在着一个非静态的公有成员,该成员与接口成员个非静态的公有成员,该成员与接口成员 MembeMemberr 相对应,则由它来实现相对应,则由它来实现 Member Member 成员。成员。
(( 33)、如果上述条件仍不满足,则在类)、如果上述条件仍不满足,则在类 CC 定义的定义的基类列表中寻找一个基类列表中寻找一个 C C 的基类的基类 DD,用,用 DD来代替来代替 CC 。。
![Page 126: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/126.jpg)
(( 44 )、重复步骤)、重复步骤 1-- 31-- 3,遍历,遍历 CC 的所有直接基类和的所有直接基类和非直接基类,直到找到一个满足条件的类的成员。非直接基类,直到找到一个满足条件的类的成员。(( 55)、如果仍然没有找到,则报告错误。)、如果仍然没有找到,则报告错误。
下面是一个调用基类方法来实现接口成员的例下面是一个调用基类方法来实现接口成员的例子。类子。类 Class2 Class2 实现了接口实现了接口 Interface1Interface1 ,类,类 ClassClass2 2 的基类的基类 Class1 Class1 的成员也参与了接口的映射,也的成员也参与了接口的映射,也就是说类就是说类 Class2 Class2 在对接口在对接口 Interface1Interface1 进行实现进行实现时,使用了类时,使用了类 Class1Class1提供的成员方法提供的成员方法 FF来实现接来实现接口口 Interface1Interface1 的成员方法的成员方法 FF::
interface Interface1 interface Interface1 {{ void F( ) ;void F( ) ; }} class Class1 class Class1
![Page 127: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/127.jpg)
{ public void F( ) { }{ public void F( ) { } public void G( ) { } public void G( ) { }}}class Class2: Class1, Interface1 class Class2: Class1, Interface1 { new public void G( ) {}{ new public void G( ) {}}}
注意:接口的成员包括它自己定义的成员,而注意:接口的成员包括它自己定义的成员,而且包括该接口所有父接口定义的成员。在接口映射且包括该接口所有父接口定义的成员。在接口映射时,不仅要对接口定义体中显式定义的所有成员进时,不仅要对接口定义体中显式定义的所有成员进行映射,而且要对隐式地从父接口那里继承来的所行映射,而且要对隐式地从父接口那里继承来的所有接口成员进行映射。有接口成员进行映射。
在进行接口映射时,还要注意下面两点:在进行接口映射时,还要注意下面两点:
![Page 128: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/128.jpg)
11 、在决定由类中的哪个成员来实现接口成员时,、在决定由类中的哪个成员来实现接口成员时,类中显式说明的接口成员比其它成员优先实现。类中显式说明的接口成员比其它成员优先实现。22、使用、使用 PrivatePrivate 、、 protectedprotected 和和 staticstatic修饰符的修饰符的成员不能参与实现接口映射。成员不能参与实现接口映射。我们对我们对 C#C# 的接口有了较全面的认识,基本掌握了的接口有了较全面的认识,基本掌握了怎样应用怎样应用 C#C# 的接口编程,但事实上,的接口编程,但事实上, C#C# 的不仅仅的不仅仅应用于应用于 .NET.NET平台,它同样支持以前的平台,它同样支持以前的 COMCOM,可以,可以实现实现 COMCOM类到类到 .NET.NET类的转换,如类的转换,如 C#C#调用调用 APIAPI。。我们将在下面的章节中讲解。我们将在下面的章节中讲解。
![Page 129: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/129.jpg)
4.4.2.64.4.2.6 接口转换接口转换
![Page 130: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/130.jpg)
C#C# 中不仅支持中不仅支持 .Net .Net 平台,而且支持平台,而且支持 COMCOM平台。平台。为了支持 为了支持 COMCOM和和 .Net.Net ,, C# C# 包含一种称为属性的包含一种称为属性的独特语言特性。一个属性实际上就是一个 独特语言特性。一个属性实际上就是一个 C# C# 类,类,它通过修饰源代码来提供元信息。属性使 它通过修饰源代码来提供元信息。属性使 C# C# 能够能够支持特定的技术,如 支持特定的技术,如 COM COM 和 和 .Net.Net ,而不会干扰,而不会干扰语言规范本身。语言规范本身。 C# C# 提供将提供将 COMCOM接口转换为 接口转换为 C#C# 接接口的属性类。另一些属性类将 口的属性类。另一些属性类将 COMCOM类转换为类转换为 C# C# 类。执行这些转换不需要任何类。执行这些转换不需要任何 IDLIDL或类工厂。或类工厂。
现在部署的任何现在部署的任何 COM COM 组件都可以在接组件都可以在接口转换中使用。通常情况下,所需的调整是口转换中使用。通常情况下,所需的调整是完全自动进行的。完全自动进行的。
![Page 131: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/131.jpg)
特别是,可以使用运行时可调用包装 特别是,可以使用运行时可调用包装 (RCW) (RCW) 从 从 .NET .NET 框架访问 框架访问 COM COM 组件。此包装将 组件。此包装将 COM COM 组件组件提供的 提供的 COM COM 接口转换为与 接口转换为与 .NET .NET 框架兼容的接口。框架兼容的接口。对于 对于 OLE OLE 自动化接口,自动化接口, RCW RCW 可以从类型库中自动可以从类型库中自动生成;对于非 生成;对于非 OLE OLE 自动化接口,开发人员可以编自动化接口,开发人员可以编写自定义 写自定义 RCWRCW ,手动将 ,手动将 COM COM 接口提供的类型映接口提供的类型映射为与 射为与 .NET .NET 框架兼容的类型。框架兼容的类型。 ◆◆使用使用 ComImportComImport 引用引用 COMCOM组件组件 COM Interop COM Interop 提供对现有 提供对现有 COM COM 组件的访问,组件的访问,
而不需要修改原始组件。使用而不需要修改原始组件。使用 ComImportComImport 引用引用COMCOM组件常包括下面 几个方面的问题:组件常包括下面 几个方面的问题:
![Page 132: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/132.jpg)
11 、创建 、创建 COM COM 对象。对象。22、确定 、确定 COM COM 接口是否由对象实现。接口是否由对象实现。33、调用 、调用 COM COM 接口上的方法。接口上的方法。44 、实现可由 、实现可由 COM COM 客户端调用的对象和接口。客户端调用的对象和接口。
(( 11 )创建 )创建 COM COM 类包装类包装 要使 要使 C# C# 代码引用代码引用 COM COM 对象和接口,需要在 对象和接口,需要在
C# C# 中包含 中包含 COM COM 接口的定义。完成此操作的最简接口的定义。完成此操作的最简单方法是使用单方法是使用 TlbImp.exeTlbImp.exe (类型库导入程序),(类型库导入程序),它是一个包括在 它是一个包括在 .NET .NET 框架 框架 SDK SDK 中的命令行工具。中的命令行工具。TlbImpTlbImp 将将 COMCOM类型库转换为 类型库转换为 .NET .NET 框架元数据,框架元数据,从而有效地创建一个可以从任何托管语言调用的托从而有效地创建一个可以从任何托管语言调用的托管包装。用管包装。用 TlbImpTlbImp创建的 创建的 .NET .NET 框架元数据可以框架元数据可以通过通过 /R/R编译器选项包括在 编译器选项包括在 C# C# 内部版本中。如果内部版本中。如果使用使用 Visual StudioVisual Studio开发环境,则只需添加对开发环境,则只需添加对 COM COM 类型库的引用,将为您自动完成此转换。类型库的引用,将为您自动完成此转换。
![Page 133: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/133.jpg)
TlbImp TlbImp 执行下列转换:执行下列转换:11 )、)、 COM coclass COM coclass 转换为具有无参数构造函数的 转换为具有无参数构造函数的 C# C# 类。类。22)、)、 COM COM 结构转换为具有公共字段的 结构转换为具有公共字段的 C# C# 结构。结构。
检查 检查 TlbImp TlbImp 输出的一种很好的方法是运行 输出的一种很好的方法是运行 .NET .NET 框架 框架 SDK SDK 命令行工具 命令行工具 Ildasm.exeIldasm.exe (( Microsoft Microsoft 中中间语言反汇编程序)来查看转换结果。间语言反汇编程序)来查看转换结果。
虽然 虽然 TlbImp TlbImp 是将 是将 COM COM 定义转换为 定义转换为 C# C# 的首的首选方法,但也不是任何时候都可以使用它(例如,选方法,但也不是任何时候都可以使用它(例如,在没有 在没有 COM COM 定义的类型库时或者 定义的类型库时或者 TlbImp TlbImp 无法无法处理类型库中的定义时,就不能使用该方法)。在处理类型库中的定义时,就不能使用该方法)。在这些情况下,另一种方法是使用 这些情况下,另一种方法是使用 C# C# 属性在 属性在 C# C# 源源代码中手动定义 代码中手动定义 COM COM 定义。创建 定义。创建 C# C# 源映射后,源映射后,只需编译 只需编译 C# C# 源代码就可产生托管包装。源代码就可产生托管包装。
![Page 134: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/134.jpg)
执行 执行 COM COM 映射需要理解的主要属性包括:映射需要理解的主要属性包括:
11 )、)、 ComImportComImport :它将类标记为在外部实现的 :它将类标记为在外部实现的 COM COM 类。类。22)、)、 GuidGuid :它用于为类或接口指定通用唯一标识符 :它用于为类或接口指定通用唯一标识符 (UUI(UUID)D) 。。
33)、)、 InterfaceTypeInterfaceType ,它指定接口是从 ,它指定接口是从 IUnknown IUnknown 还是还是从 从 IDispatch IDispatch 派生。派生。
44 )、)、 PreserveSigPreserveSig ,它指定是否应将本机返回值从 ,它指定是否应将本机返回值从 HRESHRESULT ULT 转换为 转换为 .NET .NET 框架异常。框架异常。
(( 22)声明 )声明 COM coclassCOM coclass COM coclass COM coclass 在 在 C# C# 中表示为类。这些类必须具有与中表示为类。这些类必须具有与
其关联的 其关联的 ComImport ComImport 属性。下列限制适用于这些类:属性。下列限制适用于这些类:11 )、类不能从任何其他类继承。)、类不能从任何其他类继承。22)、类不能实现任何接口。)、类不能实现任何接口。33)、类还必须具有为其设置全局唯一标识符 )、类还必须具有为其设置全局唯一标识符 (GUID) (GUID) 的 的 GG
uid uid 属性。属性。
![Page 135: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/135.jpg)
以下示例在 以下示例在 C# C# 中声明一个 中声明一个 coclasscoclass ::
// // 声明一个声明一个 COMCOM 类 类 FilgraphManagerFilgraphManager[ComImport, Guid("E436EBB3-524F-11CE-[ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770")]9F53-0020AF0BA770")]
class FilgraphManagerclass FilgraphManager{ }{ } C# C# 编译器将添加一个无参数构造函数,编译器将添加一个无参数构造函数,
可以调用此构造函数来创建 可以调用此构造函数来创建 COM coclass COM coclass 的实例。的实例。
![Page 136: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/136.jpg)
(( 33)创建 )创建 COM COM 对象对象
COM coclass COM coclass 在 在 C# C# 中表示为具有无参数构造中表示为具有无参数构造函数的类。使用 函数的类。使用 new new 运算符创建该类的实例等运算符创建该类的实例等效于在 效于在 C# C# 中调用 中调用 CoCreateInstanceCoCreateInstance 。使用以。使用以上定义的类,就可以很容易地实例化此类:上定义的类,就可以很容易地实例化此类:
class MainClassclass MainClass {{ public static void Main()public static void Main() {{ FilgraphManager filg = new FilgraphManageFilgraphManager filg = new FilgraphManage
r();r(); }} }}
![Page 137: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/137.jpg)
(( 44 )声明 )声明 COM COM 接口接口
COM COM 接口在 接口在 C# C# 中表示为具有 中表示为具有 ComImport ComImport 和 和 GuiGuid d 属性的接口。它不能在其基接口列表中包含任何接口,属性的接口。它不能在其基接口列表中包含任何接口,而且必须按照方法在 而且必须按照方法在 COM COM 接口中出现的顺序声明接口成接口中出现的顺序声明接口成员函数。员函数。
在 在 C# C# 中声明的中声明的 COMCOM接口必须包含其基接口的所有接口必须包含其基接口的所有成员的声明,成员的声明, IUnknownIUnknown 和和 IDispatchIDispatch 的成员除外(的成员除外( .NE.NET T 框架将自动添加这些成员)。从 框架将自动添加这些成员)。从 IDispatch IDispatch 派生的 派生的 COCOM M 接口必须用 接口必须用 InterfaceType InterfaceType 属性予以标记。属性予以标记。
从 从 C# C# 代码调用 代码调用 COM COM 接口方法时,公共语言运行库接口方法时,公共语言运行库必须封送与 必须封送与 COM COM 对象之间传递的参数和返回值。对于每对象之间传递的参数和返回值。对于每个 个 .NET .NET 框架类型均有一个默认类型,公共语言运行库将框架类型均有一个默认类型,公共语言运行库将使用此默认类型在 使用此默认类型在 COM COM 调用间进行封送处理时封送。例调用间进行封送处理时封送。例如,如, C# C# 字符串值的默认封送处理是封送到本机类型 字符串值的默认封送处理是封送到本机类型 LPTLPTSTRSTR(指向 (指向 TCHAR TCHAR 字符缓冲区的指针)。可以在 字符缓冲区的指针)。可以在 COM COM 接口的 接口的 C# C# 声明中使用 声明中使用 MarshalAs MarshalAs 属性重写默认封送处属性重写默认封送处理。理。
![Page 138: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/138.jpg)
在 在 COM COM 中,返回成功或失败的常用方法是返中,返回成功或失败的常用方法是返回一个 回一个 HRESULTHRESULT,并在 ,并在 MIDL MIDL 中有一个标记为中有一个标记为 "r"retval"etval" 、用于方法的实际返回值的 、用于方法的实际返回值的 out out 参数。在 参数。在 CC## (和 (和 .NET .NET 框架)中,指示已经发生错误的标准框架)中,指示已经发生错误的标准方法是引发异常。方法是引发异常。
默认情况下,默认情况下, .NET .NET 框架为由其调用的 框架为由其调用的 COM COM 接口方法在两种异常处理类型之间提接口方法在两种异常处理类型之间提供自动映射。供自动映射。返回值更改为标记为 返回值更改为标记为 retval retval 的参数的的参数的
签名(如果方法没有标记为 签名(如果方法没有标记为 retval retval 的参数,的参数,则为 则为 voidvoid )。)。
![Page 139: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/139.jpg)
标记为 标记为 retval retval 的参数从方法的参数列表中剥离。的参数从方法的参数列表中剥离。任何非成功返回值都将导致引发 任何非成功返回值都将导致引发 System.COMExceSystem.COMException ption 异常。异常。此示例显示用 此示例显示用 MIDL MIDL 声明的 声明的 COM COM 接口以及用 接口以及用 C# C# 声明的同一接口(注意这些方法使用 声明的同一接口(注意这些方法使用 COM COM 错误处错误处理方法)。理方法)。
◆◆在 在 .NET .NET 框架程序中通过框架程序中通过 DllImportDllImport使使用 用 Win32 APIWin32 API
.NET .NET 框架程序可以通过静态 框架程序可以通过静态 DLL DLL 入口点入口点的方式来访问本机代码库。的方式来访问本机代码库。 DllImport DllImport 属性用属性用于指定包含外部方法的实现的于指定包含外部方法的实现的 dll dll 位置。 位置。
![Page 140: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/140.jpg)
DllImport DllImport 属性定义如下:属性定义如下:namespace System.Runtime.InteropServicesnamespace System.Runtime.InteropServices{ [AttributeUsage(AttributeTargets.Method)]{ [AttributeUsage(AttributeTargets.Method)] public class DllImportAttribute: System.Attributepublic class DllImportAttribute: System.Attribute { public DllImportAttribute(string dllName) {...}{ public DllImportAttribute(string dllName) {...} public CallingConvention CallingConvention;public CallingConvention CallingConvention; public CharSet CharSet;public CharSet CharSet; public string EntryPoint;public string EntryPoint; public bool ExactSpelling;public bool ExactSpelling; public bool PreserveSig;public bool PreserveSig; public bool SetLastError;public bool SetLastError; public string Value { get {...} }public string Value { get {...} } }}}}
![Page 141: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/141.jpg)
说明:说明:
11 、、 DllImportDllImport 只能放置在方法声明上。只能放置在方法声明上。22、、 DllImportDllImport 具有单个定位参数:指定包含被导入方法的 具有单个定位参数:指定包含被导入方法的
dll dll 名称的 名称的 dllName dllName 参数。参数。33、、 DllImportDllImport 具有五个命名参数:具有五个命名参数:aa 、、 CallingConventionCallingConvention参数指示入口点的调用约定。如果参数指示入口点的调用约定。如果未指定未指定 CallingConventionCallingConvention ,则使用默认值,则使用默认值 CallingConvCallingConvention.Winapiention.Winapi 。。
bb 、、 CharSetCharSet参数指示用在入口点中的字符集。如果未指定参数指示用在入口点中的字符集。如果未指定CharSetCharSet ,则使用默认值,则使用默认值 CharSet.AutoCharSet.Auto 。。
cc 、、 EntryPoint EntryPoint 参数给出参数给出 dlldll 中入口点的名称。如果未指定中入口点的名称。如果未指定EntryPointEntryPoint ,则使用方法本身的名称。,则使用方法本身的名称。
dd 、、 ExactSpelling ExactSpelling 参数指示 参数指示 EntryPoint EntryPoint 是否必须与指示是否必须与指示的入口点的拼写完全匹配。如果未指定 的入口点的拼写完全匹配。如果未指定 ExactSpellingExactSpelling ,,则使用默认值 则使用默认值 falsefalse 。。
![Page 142: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/142.jpg)
ff、、 SetLastError SetLastError 参数指示方法是否保留 参数指示方法是否保留 Win3Win32"2"上一错误上一错误 "" 。如果未指定 。如果未指定 SetLastErrorSetLastError ,则使,则使用默认值 用默认值 falsefalse 。。
44 、它是一次性属性类。、它是一次性属性类。55、此外,用 、此外,用 DllImport DllImport 属性修饰的属性修饰的
方法必须具有 方法必须具有 extern extern 修饰符。修饰符。面向对象的编程语言几乎都用到了抽象面向对象的编程语言几乎都用到了抽象
类这一概念,抽象类为实现抽象事物提供类这一概念,抽象类为实现抽象事物提供了更大的灵活性。了更大的灵活性。 C#C# 也不例外也不例外 , C#, C#通过覆通过覆盖虚接口的技术深化了抽象类的应用。盖虚接口的技术深化了抽象类的应用。
![Page 143: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/143.jpg)
4.4.2.74.4.2.7 覆盖虚接口覆盖虚接口
![Page 144: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/144.jpg)
有时候我们需要表达一种抽象的东西,它是一些有时候我们需要表达一种抽象的东西,它是一些东西的概括,但我们又不能真正的看到它成为一东西的概括,但我们又不能真正的看到它成为一个实体在我们眼前出现,为此面向对象的编程语个实体在我们眼前出现,为此面向对象的编程语言便有了抽象类的概念。言便有了抽象类的概念。 C#C# 作为一个面向对象作为一个面向对象的语言,必然也会引入抽象类这一概念。接口和的语言,必然也会引入抽象类这一概念。接口和抽象类使您可以创建组件交互的定义。通过接口,抽象类使您可以创建组件交互的定义。通过接口,可以指定组件必须实现的方法,但不实际指定如可以指定组件必须实现的方法,但不实际指定如何实现方法。抽象类使您可以创建行为的定义,何实现方法。抽象类使您可以创建行为的定义,同时提供用于继承类的一些公共实现。对于在组同时提供用于继承类的一些公共实现。对于在组件中实现多态行为,接口和抽象类都是很有用的件中实现多态行为,接口和抽象类都是很有用的工具。工具。
一个抽象类必须为类的基本类列表中列出的接一个抽象类必须为类的基本类列表中列出的接口的所有成员提供实现程序。但是,一个抽象类口的所有成员提供实现程序。但是,一个抽象类被允许把接口方法映射到抽象方法中。例如:被允许把接口方法映射到抽象方法中。例如:
![Page 145: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/145.jpg)
interface IMethods interface IMethods {{ void F(); void F(); void G(); void G();}}abstract class C: Imethodsabstract class C: Imethods{{ public abstract void F(); public abstract void F(); public abstract void G(); public abstract void G();}} 这里, 这里, IMethods IMethods 的实现函数把的实现函数把 FF和和 GG映射映射到抽象方法中,它们必须在从到抽象方法中,它们必须在从 CC 派生的非抽象类派生的非抽象类中被覆盖。中被覆盖。
![Page 146: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/146.jpg)
注意显式接口成员实现函数不能是抽象的,但是注意显式接口成员实现函数不能是抽象的,但是显式接口成员实现函数当然可以调用抽象方法。例显式接口成员实现函数当然可以调用抽象方法。例如:如:
interface Imethodsinterface Imethods{ void F();{ void F(); void G();void G();}}abstract class C: Imethodsabstract class C: Imethods{ void IMethods.F() { FF(); }{ void IMethods.F() { FF(); } void IMethods.G() { GG(); }void IMethods.G() { GG(); } protected abstract void FF();protected abstract void FF(); protected abstract void GG();protected abstract void GG();}}
![Page 147: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/147.jpg)
这里,从这里,从 CC 派生的非抽象类要覆盖派生的非抽象类要覆盖 FFFF和和 GGGG, , 因此提供了因此提供了 IMethodsIMethods 的实际实现程序。的实际实现程序。
4.5 4.5 集合与索引器集合与索引器 4.5.1 4.5.1 集合集合 集合基本上是由一群相同类型的对象所组成的。集合基本上是由一群相同类型的对象所组成的。
利用集合,可以使用相同的语法,一次处理多个对利用集合,可以使用相同的语法,一次处理多个对象。象。 .NET.NET对于集合的支持,集中于对于集合的支持,集中于 System.ColleSystem.Collectionction命名空间里的一组集合接口以及实现这些接命名空间里的一组集合接口以及实现这些接口的派生类;例如,口的派生类;例如, StackStack 类实现类实现 ICollectionICollection 接接口并且提供后进先出(口并且提供后进先出( last-in first-outlast-in first-out )的数据结)的数据结构集合对象,构集合对象, HashtableHashtable 类则实现类则实现 IdictionaryIdictionary 接接口,为一应用散列算法,提供高效率搜索索引键值口,为一应用散列算法,提供高效率搜索索引键值得字典型键值数据集合;这些不同的类与接口,定得字典型键值数据集合;这些不同的类与接口,定义了实现集合所需的功能。义了实现集合所需的功能。
![Page 148: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/148.jpg)
要了解要了解 .NET.NET对于集合所提供的支持,首先必须从对于集合所提供的支持,首先必须从 IIEnumerableEnumerable 以及以及 IEnumeratorIEnumerator 这两个接口开始进这两个接口开始进行说明,其提供了从集合中,存取元素对象的基础行说明,其提供了从集合中,存取元素对象的基础功能,定义列举集合内所含元素的相关方法。所有功能,定义列举集合内所含元素的相关方法。所有的集合类,均会继承的集合类,均会继承 IEnumerableIEnumerable 这个接口,这样这个接口,这样做让集合对象能够支持做让集合对象能够支持 foreachforeach 语法,支持在集合语法,支持在集合中利用循环,一一浏览列举其中元素的功能;中利用循环,一一浏览列举其中元素的功能; IenuIenumeratormerator由由 IEnumerableIEnumerable 接口所定义的接口所定义的 GetEnumeGetEnumeratorrator 方法所取得,其定义了存取集合元素所需的相方法所取得,其定义了存取集合元素所需的相关方法。关方法。
ICollectionICollection 接口是所有集合的基类,继承接口是所有集合的基类,继承 IEnumIEnumerableerable 接口,拥有最基础的集合列举功能,另外同接口,拥有最基础的集合列举功能,另外同时定义了各种集合所需的共同方法以及成员属性,为时定义了各种集合所需的共同方法以及成员属性,为整个集合架构里的核心接口,其他的集合必须继承这整个集合架构里的核心接口,其他的集合必须继承这个接口,实现其定义的相关成员,提供特定类型集合个接口,实现其定义的相关成员,提供特定类型集合所需的功能。所需的功能。
![Page 149: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/149.jpg)
IListIList 接口以接口以 ICollectionICollection 接口为基础,除了继承接口为基础,除了继承 IICollectionCollection 接口所提供的方法,并且根据自身需求接口所提供的方法,并且根据自身需求另外定义其专属得方法成员,主要提供以索引存取另外定义其专属得方法成员,主要提供以索引存取集合元素的操作支持。集合元素的操作支持。
IDictionaryIDictionary 接口同样继承了接口同样继承了 ICollectionICollection 接口,接口,支持以键支持以键 //值对的方式对集合的元素作存取,继承值对的方式对集合的元素作存取,继承这个接口的集合对象,将会存储成对得键这个接口的集合对象,将会存储成对得键 //值提供值提供作元素存取。作元素存取。
IEnumeratorIEnumerator 接口为提供键接口为提供键 //值对的枚举集合接值对的枚举集合接口,一个支持以键口,一个支持以键 //值对方式存储的集合,其值对方式存储的集合,其 GetGetEnumeratorEnumerator 方法将会返回这个类型的枚举类型对方法将会返回这个类型的枚举类型对象。象。
IComparerIComparer 接口定义了比较两个集合中对象的接口定义了比较两个集合中对象的比较方法。比较方法。
![Page 150: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/150.jpg)
◆◆ICollectionICollection 接口是接口是 .NET Framework.NET Framework 提供提供的标准集合接口的标准集合接口 所有的集合都会实现这个接口,它定义所有的集合都会实现这个接口,它定义
了所有集合必须具备的方法与属性成员,了所有集合必须具备的方法与属性成员,其中包含一个方法其中包含一个方法 CopyToCopyTo ,定义的形式,定义的形式如下:如下:
void CopyTo(void CopyTo( Array array,Array array, int indexint index ););
![Page 151: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/151.jpg)
注意:注意: arrayarray 作为从 作为从 ICollection ICollection 复制的元素的复制的元素的目标位置的一维目标位置的一维 ArrayArray 。。 ArrayArray必须具有从零开始必须具有从零开始的索引。的索引。
IndexIndex:: arrayarray 中的从零开始的索引,从此处开始中的从零开始的索引,从此处开始复制。复制。
ICollectionICollection 接口另外提供了接口另外提供了 33个属性,其中比较个属性,其中比较重要且经常被用到的是重要且经常被用到的是 CountCount 属性,其定义如下:属性,其定义如下:
Count { get ; }Count { get ; } 这个属性成员,用以返回集合中所存在的元素个这个属性成员,用以返回集合中所存在的元素个
数值。数值。 ◆ ◆IListIList 接口与实现类接口与实现类
![Page 152: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/152.jpg)
表示可按照索引单独访问的一组对象。表示可按照索引单独访问的一组对象。 IListIList 是是 IICollectionCollection 接口的子代,并且是所有列表的抽象基接口的子代,并且是所有列表的抽象基类。类。 IListIList 实现有三种类别:只读、固定大小、可变实现有三种类别:只读、固定大小、可变大小。无法修改只读大小。无法修改只读 IListIList 。固定大小的。固定大小的 IListIList 不允不允许添加或移除元素,但允许修改现有元素。可变大许添加或移除元素,但允许修改现有元素。可变大小的小的 IListIList允许添加、移除和修改元素。其格式如下:允许添加、移除和修改元素。其格式如下:
public interface IList : ICollection, IEnumepublic interface IList : ICollection, IEnumerablerable
其主要的方法成员:其主要的方法成员:(( 11 )) int Add(object value); int Add(object value); 将一项添加到 将一项添加到 IListIList 。。
(( 22)) void Clear();void Clear(); 从 从 IList IList 中移除所有项。中移除所有项。
![Page 153: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/153.jpg)
(( 33)) bool Contains(object value);bool Contains(object value);确定 确定 IList IList 是否包含特定的值。是否包含特定的值。(( 44 )) int IndexOf(object value); int IndexOf(object value); 确定 确定 IList IList 中特中特定项的索引。定项的索引。(( 55)) void Insert(int index, object value); void Insert(int index, object value); 在 在 IIList List 的指定位置处插入一项。的指定位置处插入一项。(( 66)) void Remove(object value); void Remove(object value); 从 从 IList IList 移除移除特定对象的第一个匹配项。特定对象的第一个匹配项。(( 77)) void RemoveAt(int index); void RemoveAt(int index); 在指定的索引在指定的索引处移除 处移除 IList IList 项。项。
◆◆IDictionary IDictionary 类是键类是键 // 值对的集合的基接口值对的集合的基接口
![Page 154: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/154.jpg)
每个元素是一个存储在每个元素是一个存储在 DictionaryEntryDictionaryEntry 对象对象中的键中的键 //值对。值对。
每个关联必须具有非空引用(每个关联必须具有非空引用( Visual Basic Visual Basic 中中为为 NothingNothing )的唯一键,但关联的值可以为任何)的唯一键,但关联的值可以为任何对象引用,包括空引用 对象引用,包括空引用 (Nothing)(Nothing) 。。 IDictionaryIDictionary接口允许对所包含的键和值进行枚举,但这并不接口允许对所包含的键和值进行枚举,但这并不意味着任何特定的排序顺序。意味着任何特定的排序顺序。
IDictionaryIDictionary 实现有三种类别:只读、固定大小、实现有三种类别:只读、固定大小、可变大小。无法修改只读 可变大小。无法修改只读 IDictionaryIDictionary 。固定大。固定大小的小的 IDictionaryIDictionary 不允许添加或移除元素,但允许不允许添加或移除元素,但允许修改现有元素。可变大小的修改现有元素。可变大小的 IDictionaryIDictionary允许添加、允许添加、移除和修改元素。移除和修改元素。
![Page 155: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/155.jpg)
C# C# 语言中的语言中的 foreachforeach 语句(在语句(在 Visual BasicVisual Basic 中中为为 foreachforeach )需要集合中每个元素的类型。由于)需要集合中每个元素的类型。由于 IDiIDictionaryctionary 的每个元素都是一个键的每个元素都是一个键 //值对,因此元素值对,因此元素类型既不是键的类型,也不是值的类型。而是类型既不是键的类型,也不是值的类型。而是 DictiDictionaryEntryonaryEntry 类型。例如:类型。例如:foreach (DictionaryEntry myDE in myHashtable) foreach (DictionaryEntry myDE in myHashtable)
{...}{...}[Visual Basic] [Visual Basic] Dim myDE As DictionaryEntryDim myDE As DictionaryEntryFor Each myDE In myHashtableFor Each myDE In myHashtable ......Next myDENext myDE
![Page 156: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/156.jpg)
foreachforeach 语句是对枚举数的包装,它只允许从集语句是对枚举数的包装,它只允许从集合中读取,不允许写入集合。合中读取,不允许写入集合。◆◆ IEnumeratorIEnumerator 接口接口
IEnumeratorIEnumerator 是所有枚举数的基接口。是所有枚举数的基接口。 枚举数只允许读取集合中的数据。枚举数无法用枚举数只允许读取集合中的数据。枚举数无法用于修改基础集合。于修改基础集合。
最初,枚举数被定位于集合中第一个元素的前面。最初,枚举数被定位于集合中第一个元素的前面。ResetReset 也将枚举数返回到此位置。在此位置,调用也将枚举数返回到此位置。在此位置,调用 CCurrenturrent 会引发异常。因此,在读取会引发异常。因此,在读取 CurrentCurrent 的值之的值之前,必须调用前,必须调用 MoveNextMoveNext 将枚举数提前到集合的第将枚举数提前到集合的第一个元素。一个元素。
在调用在调用 MoveNextMoveNext或或 ResetReset 之前,之前, CurrentCurrent返回返回同一对象。同一对象。 MoveNextMoveNext 将将 CurrentCurrent 设置为下一个元设置为下一个元素。素。
![Page 157: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/157.jpg)
在传递到集合的末尾之后,枚举数放在集合中在传递到集合的末尾之后,枚举数放在集合中最后一个元素后面,且调用最后一个元素后面,且调用 MoveNextMoveNext 会返回会返回 falfalsese 。如果最后一次调用。如果最后一次调用 MoveNextMoveNext返回返回 falsefalse ,,则调用则调用 CurrentCurrent 会引发异常。若要再次将会引发异常。若要再次将 CurrenCurrentt 设置为集合的第一个元素,可以调用设置为集合的第一个元素,可以调用 ResetReset ,,然后再调用然后再调用 MoveNextMoveNext 。。
只要集合保持不变,枚举数就将保持有效。如只要集合保持不变,枚举数就将保持有效。如果对集合进行了更改(例如添加、修改或删除元果对集合进行了更改(例如添加、修改或删除元素),则该枚举数将失效且不可恢复,并且下一素),则该枚举数将失效且不可恢复,并且下一次对次对 MoveNextMoveNext或或 ResetReset 的调用将引发的调用将引发 InvalidOInvalidOperationExceptionperationException 。如果在。如果在 MoveNextMoveNext 和和 CurrCurrentent 之间修改集合,那么即使枚举数已经无效,之间修改集合,那么即使枚举数已经无效, CCurrenturrent 也将返回它所设置成的元素。也将返回它所设置成的元素。
![Page 158: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/158.jpg)
枚举数没有对集合的独占访问权;因此,枚举一枚举数没有对集合的独占访问权;因此,枚举一个集合在本质上不是一个线程安全的过程。甚至在个集合在本质上不是一个线程安全的过程。甚至在对集合进行同步处理时,其他线程仍可以修改该集对集合进行同步处理时,其他线程仍可以修改该集合,这会导致枚举数引发异常。若要在枚举过程中合,这会导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合,保证线程安全,可以在整个枚举过程中锁定集合,或者捕捉由于其他线程进行的更改而引发的异常。或者捕捉由于其他线程进行的更改而引发的异常。
IEnumeratorIEnumerator 接口定义了一个只读属性,定义如下:接口定义了一个只读属性,定义如下: object Current {get;}object Current {get;} 这个属性取得目前列举的元素,你无法利用这个这个属性取得目前列举的元素,你无法利用这个
属性,修改集合中的对象元素,属性,修改集合中的对象元素, IEnumeratorIEnumerator 接口接口另外定义了一个浏览集合对象的方法另外定义了一个浏览集合对象的方法 MoveNextMoveNext ,,任何实现这个接口的类,均必须实现此方法,以提任何实现这个接口的类,均必须实现此方法,以提供浏览集合对象的功能,定义如下:供浏览集合对象的功能,定义如下:
![Page 159: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/159.jpg)
bool MoveNext();bool MoveNext();此方法返回一个布尔数据类型的结果,当浏览至此方法返回一个布尔数据类型的结果,当浏览至集合结尾的时候,这个值会返回集合结尾的时候,这个值会返回 flaseflase ,否则均,否则均为为 truetrue 。。
IEnumeratorIEnumerator 接口另外定义的一个方法为接口另外定义的一个方法为 ResetReset ,,这个方法将枚举数设置为其初始位置,该位置位这个方法将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。于集合中第一个元素之前。◆◆IComparerIComparer 接口接口 公开一种比较两个对象的方法。公开一种比较两个对象的方法。 此接口与 此接口与 Array.Sort Array.Sort 和 和 Array.BinarySearch Array.BinarySearch 方方
法一起使用。它提供一种自定义集合排序顺序的法一起使用。它提供一种自定义集合排序顺序的方法。方法。
此接口的默认实现为 此接口的默认实现为 Comparer Comparer 类。类。
![Page 160: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/160.jpg)
4.5.2 4.5.2 索引器索引器 索引器可以像数组那样对对象进行索引访问。索引器可以像数组那样对对象进行索引访问。
在在 CC 和和 C++C++ 中,没有索引器的概念,它是在中,没有索引器的概念,它是在 C#C#首次提出的。首次提出的。
索引器索引器 (indexer)(indexer)使得可以像数组那样对对象使得可以像数组那样对对象使用下标。已为我们提供了通过索引方式方便地使用下标。已为我们提供了通过索引方式方便地访问类的数据信息的方法。访问类的数据信息的方法。
11 、声明、声明 还是让我们先来看一下索引器的声明格式:还是让我们先来看一下索引器的声明格式: [[ 修饰符修饰符 ] ] 数据类型 数据类型 this [int index] {this [int index] {访问函数访问函数
体代码体代码 }} 索引器可以使用的修饰符有:索引器可以使用的修饰符有: newnew 、、 publicpublic 、、 pp
rotectedrotected 、、 internalinternal 、、 privateprivate 、、 virtualvirtual 、、 seasealedled 、、 overrideoverride 和和 abstractabstract 。。
![Page 161: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/161.jpg)
一对大括号“一对大括号“ {}”{}” 之间是索引器的访问声明,使之间是索引器的访问声明,使用用 getget关键字和关键字和 setset关键字定义了对被索引的元关键字定义了对被索引的元素的读写权限。素的读写权限。
在许多情况下,某些数据信息应该是属于类或类的在许多情况下,某些数据信息应该是属于类或类的实例所私有的,需要限制对这些信息的访问。而实例所私有的,需要限制对这些信息的访问。而我们有时又不希望这类数据对外界完全封闭。和我们有时又不希望这类数据对外界完全封闭。和属性一样,索引器为我们提供了控制访问权限的属性一样,索引器为我们提供了控制访问权限的另一种办法。另一种办法。
案例案例:利用索引器访问一个给定的学生数组,输出:利用索引器访问一个给定的学生数组,输出学生姓名学生姓名
目标目标:掌握索引器的基本使用方法:掌握索引器的基本使用方法步骤步骤::
![Page 162: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/162.jpg)
11 、启动、启动 VS.NETVS.NET,新建一个控制台应用程序,名称,新建一个控制台应用程序,名称填写为“填写为“ MatrixMultiplyTest”MatrixMultiplyTest” ,位置设置为“,位置设置为“ c:\c:\CSharpSamples\chp4”CSharpSamples\chp4” 。。
22、在代码设计窗口中编辑、在代码设计窗口中编辑 Class1.csClass1.cs 。其中代码编。其中代码编写如下:写如下:
using System;using System;namespace IndexStudTestnamespace IndexStudTest{ class IndexStud{ class IndexStud
{ private string[] sname;{ private string[] sname;public IndexStud()//public IndexStud()// 构造函数构造函数{ sname=new string[]{"Smith","Ros{ sname=new string[]{"Smith","Ros
e","Mary","Robot","Hamlat"};e","Mary","Robot","Hamlat"};}}
![Page 163: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/163.jpg)
//// 定义类定义类 IndexStudIndexStud 的索引器,用于访问存储在的索引器,用于访问存储在 snsnameame 数组中学生姓名信息数组中学生姓名信息
public string this[int index]public string this[int index]{ get{return sname[index];}{ get{return sname[index];}
set{sname[index]=value;}set{sname[index]=value;}}}
}}public class Apppublic class App
{ static void Main(string[] args){ static void Main(string[] args){IndexStud stud=new IndexStud();{IndexStud stud=new IndexStud();
for(int x=0;x<5;x++)for(int x=0;x<5;x++)////输出存储在数组中的对象的值输出存储在数组中的对象的值Console.Write("{0,10:c}",stud[x]);Console.Write("{0,10:c}",stud[x]);
![Page 164: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/164.jpg)
////调用索引器的调用索引器的 getget 函数,将函数,将 xx 值传送给值传送给 indexindexConsole.WriteLine();Console.WriteLine();
}}}}
}}
33、按、按 Ctrl + F5Ctrl + F5编译并运行该程序,效果如编译并运行该程序,效果如图图 4-64-6所示。所示。
图 4-6 程序运行结果
![Page 165: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/165.jpg)
4.6 4.6 异常处理异常处理 4.6.1 4.6.1 异常类异常类在在 C# C# 里,异常处理就是里,异常处理就是 C# C# 为处理错误情为处理错误情
况提供的一种机制。它为每种错误情况提供了定况提供的一种机制。它为每种错误情况提供了定制的处理方式,并且把标识错误的代码与处理错制的处理方式,并且把标识错误的代码与处理错误的代码分离开来。误的代码分离开来。
对对 .NET.NET类来说,一般的异常类类来说,一般的异常类 System.ExcSystem.Exceptioneption 派生于派生于 System.ObjectSystem.Object 。还有许多定义好。还有许多定义好的异常类(如:的异常类(如: System.SystemExceptionSystem.SystemException 、、 SySystem.ApplicationExceptionstem.ApplicationException等),他们又派生等),他们又派生于于 System.ExceptionSystem.Exception 类。其中类。其中 System.ApplicatSystem.ApplicationExceptionionException 类是第三方定义的异常类,如果我类是第三方定义的异常类,如果我们要自定义异常类,那么就应派生于它。们要自定义异常类,那么就应派生于它。
![Page 166: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/166.jpg)
在代码中对异常进行处理,一般要使用三个代码在代码中对异常进行处理,一般要使用三个代码块:块:◆◆Try Try 块的代码是程序中可能出现错误的操作部分。块的代码是程序中可能出现错误的操作部分。◆◆Catch Catch 块的代码是用来处理各种错误的部分(可块的代码是用来处理各种错误的部分(可
以有多个)。必须正确排列捕获异常的以有多个)。必须正确排列捕获异常的 catchcatch 子子句句 ,,范围小的范围小的 ExceptionException放在前面的放在前面的 catchcatch 。即。即如果如果 ExceptionException 之间存在继承关系,就应把子类之间存在继承关系,就应把子类的的 ExceptionException放在前面的放在前面的 catchcatch 子句中。子句中。◆◆Finally Finally 块的代码用来清理资源或执行要在块的代码用来清理资源或执行要在 trytry块块末尾执行的其他操作(可以省略)。且无论是否末尾执行的其他操作(可以省略)。且无论是否产生异常,产生异常, FinallyFinally块都会执行。块都会执行。
![Page 167: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/167.jpg)
不管程序写得再好,异常都可能会发生,而不管程序写得再好,异常都可能会发生,而程序也必须能够处理可能出现的错误。所以我们程序也必须能够处理可能出现的错误。所以我们要站在异常一定可能会发生的角度来编写异常处要站在异常一定可能会发生的角度来编写异常处理程序,应对程序有可能发生的错误建立一个良理程序,应对程序有可能发生的错误建立一个良好的异常处理策略。好的异常处理策略。异常产生的时候,我们想知道的是什么原因异常产生的时候,我们想知道的是什么原因
造成的错误以及错误的相关信息。我们可以根据造成的错误以及错误的相关信息。我们可以根据实际情况抛出具体类型的异常,方便捕捉到异常实际情况抛出具体类型的异常,方便捕捉到异常时做出具体的处理。在编写代码过程中,可以使时做出具体的处理。在编写代码过程中,可以使用系统已定义的相关异常类以及自定义的异常类用系统已定义的相关异常类以及自定义的异常类来实例化并抛出我们需要的异常。如一个不可能来实例化并抛出我们需要的异常。如一个不可能实现的接口,我们可以抛出“实现的接口,我们可以抛出“ System.NotSuppSystem.NotSupportedExceptiion”ortedExceptiion” 的异常来告诉接口的调用者。的异常来告诉接口的调用者。
![Page 168: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/168.jpg)
在处理异常的时候,我们应该将可处理的具在处理异常的时候,我们应该将可处理的具体异常分别在体异常分别在 catch catch 块中做出相应处理,否则程块中做出相应处理,否则程序将终止运行。针对每一种异常,以不同方式处序将终止运行。针对每一种异常,以不同方式处理,避免对所有异常做出一样的处理。理,避免对所有异常做出一样的处理。
并且在异常产生时,给用户一个友好的提示并且在异常产生时,给用户一个友好的提示(普通用户对异常的具体内容是不明白的,这就(普通用户对异常的具体内容是不明白的,这就需要我们给出相关的简要信息和解决方案,或则需要我们给出相关的简要信息和解决方案,或则告之联系管理员等。),并在可能的情况下给用告之联系管理员等。),并在可能的情况下给用户提供可能的选择(终止,重试,忽略),让用户提供可能的选择(终止,重试,忽略),让用户来决定程序的运行方向。同时,要将异常做日户来决定程序的运行方向。同时,要将异常做日志记录。但不是所有异常都是必须记录的,比如志记录。但不是所有异常都是必须记录的,比如一些可预料并且能让程序解决的错误我们就不需一些可预料并且能让程序解决的错误我们就不需要记录它。要记录它。
![Page 169: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/169.jpg)
记录异常我们可以采取如下一些方式:记录异常我们可以采取如下一些方式:◆◆在文件中记录异常。便于技术人员查看所发生的在文件中记录异常。便于技术人员查看所发生的异常,从而日后对程序进行改进。异常,从而日后对程序进行改进。◆◆在数据库中记录异常。数据库支持查询,这样在在数据库中记录异常。数据库支持查询,这样在后期就能够对异常进行分类查询等操作,便于查后期就能够对异常进行分类查询等操作,便于查看与管理。看与管理。◆◆在在 EventlogEventlog 中记录异常:能够远程操作,方便中记录异常:能够远程操作,方便系统管理员监控所有计算机的异常。系统管理员监控所有计算机的异常。
除了具体的、可预料到的异常外,还有未预料除了具体的、可预料到的异常外,还有未预料的异常。像这类异常是我们不愿意看到了,但发的异常。像这类异常是我们不愿意看到了,但发生了也只能暂时结束程序的运行,这里如果做好生了也只能暂时结束程序的运行,这里如果做好了日志就能为我们解决和调试问题带来了方便。了日志就能为我们解决和调试问题带来了方便。还有,要避免使用了还有,要避免使用了 try-catchtry-catch 但没有处理异常的但没有处理异常的情况,否则就相当于给异常放行(这种情况还不情况,否则就相当于给异常放行(这种情况还不如根本就不去捕获它)。如根本就不去捕获它)。
![Page 170: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/170.jpg)
处理完异常,我们还应该注意在处理完异常,我们还应该注意在 finallyfinally块中释放块中释放相关资源、还原相关设置信息等收尾工作。相关资源、还原相关设置信息等收尾工作。
在做异常处理的时候,最好能在应用程序所有在做异常处理的时候,最好能在应用程序所有的入口处(事件处理函数,主函数,线程入口)的入口处(事件处理函数,主函数,线程入口)使用使用 try-catchtry-catch 。 但是不要在程序构造函数入口。 但是不要在程序构造函数入口处添加处添加 try-catchtry-catch ,因为此处产生异常,它自己并,因为此处产生异常,它自己并没有能力来处理,因为它还没有构造完毕,只能没有能力来处理,因为它还没有构造完毕,只能再向外层抛出异常。再向外层抛出异常。
在一般情况下使用异常机制来处理错误,能使在一般情况下使用异常机制来处理错误,能使整个程序的结构清晰、代码简单(标识错误的代整个程序的结构清晰、代码简单(标识错误的代码与处理错误代码分离),但我们也不能盲目使码与处理错误代码分离),但我们也不能盲目使用异常。而且使用异常,可能会在一定程度上影用异常。而且使用异常,可能会在一定程度上影响到程序的性能(响到程序的性能( C#C# 中使用异常一般不影响性中使用异常一般不影响性能)。对于一些简单的、能够提前避免的错误,能)。对于一些简单的、能够提前避免的错误,我们还是应该在我们还是应该在 trytry块外面及早做出处理。如:块外面及早做出处理。如:
![Page 171: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/171.jpg)
trytry{ int x = y/z; }{ int x = y/z; }catchcatch{ // ... }{ // ... }if(z == 0)if(z == 0){ Console.WriteLine("{ Console.WriteLine(" 除数不能为零除数不能为零 ");"); // ... } // ... }trytry{ int x = y/z; }{ int x = y/z; }catchcatch{ // ... }{ // ... }
![Page 172: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/172.jpg)
4.6.2 4.6.2 抛出和捕获异常抛出和捕获异常
![Page 173: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/173.jpg)
4.6.2.1 4.6.2.1 使用 使用 try try 和 和 catchcatch 捕获异捕获异常常你肯定会对一件事非常感兴趣——不要提示给用户你肯定会对一件事非常感兴趣——不要提示给用户
那令人讨厌的异常消息,以便你的应用程序继续那令人讨厌的异常消息,以便你的应用程序继续执行。要这样,你必须捕获(处理)该异常。执行。要这样,你必须捕获(处理)该异常。
这样使用的语句是这样使用的语句是 try try 和 和 catchcatch 。。 trytry 包含可能会包含可能会产生异常的语句,而产生异常的语句,而 catchcatch 处理一个异常,如果处理一个异常,如果有异常存在的话。程序清单中用有异常存在的话。程序清单中用 try try 和 和 catchcatch 为为DivideByZeroExceptionDivideByZeroException 实现异常处理。实现异常处理。
案例案例:对除数为零的捕获:对除数为零的捕获目标目标:掌握使用:掌握使用 try try 和 和 catchcatch捕获异常的方法捕获异常的方法
![Page 174: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/174.jpg)
步骤步骤::11 、启动、启动 VS.NETVS.NET,新建一个控制台应用程序,名称,新建一个控制台应用程序,名称填写为“填写为“ ZeroOverTest”ZeroOverTest” ,位置设置为“,位置设置为“ c:\CShac:\CSharpSamples\chp4”rpSamples\chp4” 。。22、在代码设计窗口中编辑、在代码设计窗口中编辑 Class1.csClass1.cs 。其中代码编。其中代码编写如下:写如下:
using System;using System;namespace ZeroOverTestnamespace ZeroOverTest{{
class ZeroOverTestclass ZeroOverTest{ static int Zero=0;{ static int Zero=0;
static void AFunction()static void AFunction(){{
int j=22/Zero;int j=22/Zero;
![Page 175: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/175.jpg)
//// 下面的语句永远不会执行下面的语句永远不会执行Console.WriteLine("In AFunction()");Console.WriteLine("In AFunction()");
}} public static void Main(string[] args)public static void Main(string[] args)
{ try{ try{ AFunction();{ AFunction();}}catch(DivideByZeroException e)catch(DivideByZeroException e){ Console.WriteLine("DivideByZer{ Console.WriteLine("DivideByZer
o {0}",e);o {0}",e);}}
}}}}
}}
![Page 176: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/176.jpg)
33、按、按 Ctrl + F5Ctrl + F5编译并运行该程序,效果如图编译并运行该程序,效果如图 4-74-7所示。所示。
如果你不事先知道哪一种异常会被预期,如果你不事先知道哪一种异常会被预期,而仍然想处于安全状态,简单地忽略异常而仍然想处于安全状态,简单地忽略异常的类型。的类型。
图 4-7 程序运行结果
![Page 177: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/177.jpg)
trytry{{ ... ...}}catchcatch{{ ... ...}}
但是,通过这个途径,你不能获得对异但是,通过这个途径,你不能获得对异常对象的访问,而该对象含有重要的出错常对象的访问,而该对象含有重要的出错信息。一般化异常处理代码象这样:信息。一般化异常处理代码象这样:
![Page 178: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/178.jpg)
trytry{{ ... ...}}catch(System.Exception e)catch(System.Exception e){{ ... ...}}
注意,你不能用注意,你不能用 refref 或或 outout 修饰符传递修饰符传递 ee 对对象给一个方法,也不能赋给它一个不同的象给一个方法,也不能赋给它一个不同的值。 值。
![Page 179: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/179.jpg)
4.6.2.2 4.6.2.2 使用使用 trytry 和和 finallyfinally 清除异清除异常常 如果你更关心清除而不是错误处理,那么如果你更关心清除而不是错误处理,那么 trytry
和和 finallyfinally 会获得你的使用。它不仅抑制了出错消会获得你的使用。它不仅抑制了出错消息,而且所有包含在息,而且所有包含在 finallyfinally块中的代码在异常被块中的代码在异常被引发后仍然会被执行。尽管程序不正常终止,但引发后仍然会被执行。尽管程序不正常终止,但你还可以为用户获取一条消息。你还可以为用户获取一条消息。
在在 C++C++ 中的中的 __leave__leave 语句是用来提前终止语句是用来提前终止 trytry语段中的执行代码,并立即跳转到语段中的执行代码,并立即跳转到 finallyfinally 语段 。语段 。C#C# 中没有中没有 __leave__leave 语句。但是,在下面例子中的语句。但是,在下面例子中的代码演示了一个你可以实现的方案。代码演示了一个你可以实现的方案。
![Page 180: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/180.jpg)
案例案例:实现:实现 __leave__leave 语句的跳转语句的跳转目标目标:掌握使用:掌握使用 trytry 和和 finallyfinally 清除异常的基本方法清除异常的基本方法步骤步骤::
11 、启动、启动 VS.NETVS.NET,新建一个控制台应用程序,名称,新建一个控制台应用程序,名称填写为“填写为“ JumpTest”JumpTest” ,位置设置为“,位置设置为“ c:\CSharpSamplc:\CSharpSamples\chp4”es\chp4” 。。
22、在代码设计窗口中编辑、在代码设计窗口中编辑 Class1.csClass1.cs 。其中的代码。其中的代码编写如下:编写如下:
using System;using System;namespace JumpTestnamespace JumpTest{{
class JumpTestclass JumpTest{{
public static void Main()public static void Main(){{
![Page 181: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/181.jpg)
trytry{ Console.WriteLine("try");{ Console.WriteLine("try");
goto __leave; }goto __leave; }finallyfinally{ Console.WriteLine("finally");{ Console.WriteLine("finally");}}__leave:__leave:
Console.WriteLine("__leavConsole.WriteLine("__leave");e");
}}}}
}}33、按、按 Ctrl + F5Ctrl + F5编译并运行该程序编译并运行该程序
![Page 182: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/182.jpg)
一个一个 gotogoto 语句不能退出一个语句不能退出一个 finallyfinally 语段。甚至语段。甚至把把 gotogoto 语句放在语句放在 trytry 语句段中,还是会立即返回语句段中,还是会立即返回控制到控制到 finallyfinally 语段。因此,语段。因此, gotogoto 只是离开了只是离开了 trytry语段并跳转到语段并跳转到 finallyfinally 语段。直到语段。直到 finallyfinally 中的代中的代码完成运行后,才能到达码完成运行后,才能到达 __leave__leave标签。标签。
4.6.2.3 4.6.2.3 使用使用 try-catch-finallytry-catch-finally 处理所有异常处理所有异常应用程序最有可能的途径是合并前面两种错误处理应用程序最有可能的途径是合并前面两种错误处理
技术——捕获错误、清除并继续执行应用程序。所有你要技术——捕获错误、清除并继续执行应用程序。所有你要做的是在出错处理代码中使用做的是在出错处理代码中使用 try try 、、 catchcatch 和和 finallyfinally 语语句。句。
案例案例:显示处理零除错误的途径:显示处理零除错误的途径目标目标:掌握使用:掌握使用 try-catch-finallytry-catch-finally 处理所有异常的处理所有异常的
基本方法基本方法步骤步骤::
![Page 183: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/183.jpg)
11 、启动、启动 VS.NETVS.NET,新建一个控制台应用程序,名称,新建一个控制台应用程序,名称填写为“填写为“ CatchITTest”CatchITTest” ,位置设置为“,位置设置为“ c:\CSharc:\CSharpSamples\chp4”pSamples\chp4” 。。22、在代码设计窗口中编辑、在代码设计窗口中编辑 Class1.csClass1.cs 。其中的代码。其中的代码编写如下:编写如下:
using System;using System;namespace CatchITTestnamespace CatchITTest{{
class CatchITclass CatchIT{{
public static void Main()public static void Main(){{
trytry{{
![Page 184: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/184.jpg)
int nTheZero = 0;int nTheZero = 0;int nResult = 10 / nTheZero;int nResult = 10 / nTheZero;
}}catch(DivideByZeroException catch(DivideByZeroException
divEx)divEx){Console.WriteLine("divide by {Console.WriteLine("divide by
zero occurred!");zero occurred!");}}catch(Exception Ex)catch(Exception Ex){{
Console.WriteLine("some other exception");Console.WriteLine("some other exception");}}finallyfinally
![Page 185: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/185.jpg)
{{}}
}}}}
}}
33、按、按 Ctrl + F5Ctrl + F5编译并运行该程序,效果如编译并运行该程序,效果如图图 4-94-9所示。所示。
图 4-9 程序运行结果
![Page 186: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/186.jpg)
4.6.2.4 4.6.2.4 抛出异常抛出异常
在在 C#C# 中,除了程序发生错误产生异常外,程中,除了程序发生错误产生异常外,程序员还可以自己为某种目的抛出异常。序员还可以自己为某种目的抛出异常。使用以下两种方式都可以抛出异常:使用以下两种方式都可以抛出异常: throw Exception_obj;throw Exception_obj; throw new ArgumentException(Exceptiothrow new ArgumentException(Exceptio
n);n);案例案例:定义一个抛出异常:定义一个抛出异常目标目标:掌握抛出异常的基本方法:掌握抛出异常的基本方法
![Page 187: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/187.jpg)
步骤步骤::11 、启动、启动 VS.NETVS.NET,新建一个控制台应用程序,名称,新建一个控制台应用程序,名称填写为“填写为“ MyClientTest”MyClientTest” ,位置设置为“,位置设置为“ c:\CShac:\CSharpSamples\chp4”rpSamples\chp4” 。。22、在代码设计窗口中编辑、在代码设计窗口中编辑 Class1.csClass1.cs 。其中的代码。其中的代码编写如下:编写如下:using System;using System;namespace MyClientTestnamespace MyClientTest{{
class MyClientclass MyClient{{
public static void Main(string[] args)public static void Main(string[] args){{
![Page 188: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/188.jpg)
trytry{ throw new DivideByZeroExce{ throw new DivideByZeroExce
ption("Invalid Division");ption("Invalid Division");}}catch(DivideByZeroException e)catch(DivideByZeroException e){ Console.WriteLine("Exceptio{ Console.WriteLine("Exceptio
n");n");}}Console.WriteLine("LAST STATEConsole.WriteLine("LAST STATE
MENT");MENT");}}
}}}}33、按、按 Ctrl + F5Ctrl + F5编译并运行该程序 编译并运行该程序
![Page 189: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/189.jpg)
在函数内部的在函数内部的 catchcatch块中,使用块中,使用 throwthrow关键关键字不带任何参数,还可以直接把接到的异常重新字不带任何参数,还可以直接把接到的异常重新发送出去,这个异常将传送到调用函数的代码中。发送出去,这个异常将传送到调用函数的代码中。
4.6.2.5 4.6.2.5 重发异常重发异常一般可以采用捕获异常,并做处理,再重发一般可以采用捕获异常,并做处理,再重发
异常的机制。异常的机制。案例案例:定义一个重发异常:定义一个重发异常目标目标:掌握重发异常的基本方法:掌握重发异常的基本方法步骤步骤::11 、启动、启动 VS.NETVS.NET,新建一个控制台应用程序,,新建一个控制台应用程序,
名称填写为“名称填写为“ SummerTest”SummerTest” ,位置设置为“,位置设置为“ c:c:\CSharpSamples\chp4”\CSharpSamples\chp4” 。。22、在代码设计窗口中编辑、在代码设计窗口中编辑 Class1.csClass1.cs 。其中。其中
的代码编写如下:的代码编写如下:
![Page 190: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/190.jpg)
using System;using System;namespace SummerTestnamespace SummerTest
{ public class Summer{ public class Summer{ int sum=0;{ int sum=0;
int count=0;int count=0;float average;float average;public void DoAverage()public void DoAverage(){ try{ try
{ average=sum/count;{ average=sum/count;}}catch(DivideByZeroException e)catch(DivideByZeroException e){{
![Page 191: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/191.jpg)
}}}}
}}class Testclass Test{ public static void Main(string[] args){ public static void Main(string[] args)
{ Summer summer=new Summer();{ Summer summer=new Summer();
trytry{ summer.DoAverage();{ summer.DoAverage();}}catch(Exception e)catch(Exception e){{
Console.WriteLine("Exception {0}",e);Console.WriteLine("Exception {0}",e);}}
![Page 192: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/192.jpg)
}}}}
}}
33、按、按 Ctrl + F5Ctrl + F5编译并运行该程序,效果如编译并运行该程序,效果如图图 4-114-11 所示。所示。
图 4-11 程序运行结果
![Page 193: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/193.jpg)
4.7 4.7 委托和事件委托和事件
4.7.1 4.7.1 委托委托 C#C# 中的委托类似于中的委托类似于 CC或或 C++C++ 中的函数中的函数
指针。使用委托使程序员可以将方法引用指针。使用委托使程序员可以将方法引用封装在委托对象内。然后可以将该委托对封装在委托对象内。然后可以将该委托对象传递给可调用所引用方法的代码,而不象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方法。与必在编译时知道将调用哪个方法。与 CC或或C++C++ 中的函数指针不同,委托是面向对象、中的函数指针不同,委托是面向对象、类型安全的,并且是安全的。类型安全的,并且是安全的。
![Page 194: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/194.jpg)
委托的声明格式如下:委托的声明格式如下:[ [ 属性 属性 ] [ ] [ 委托修饰符 委托修饰符 ] delegate ] delegate 返回类型 标识返回类型 标识
符 符 ( [( [形参表形参表 ] );] );委托声明定义一种类型,它用一组特定的参数以及返委托声明定义一种类型,它用一组特定的参数以及返
回类型封装方法。对于静态方法,委托对象封装要调用的回类型封装方法。对于静态方法,委托对象封装要调用的方法。对于实例方法,委托对象同时封装一个实例和该实方法。对于实例方法,委托对象同时封装一个实例和该实例上的一个方法。如果您有一个委托对象和一组适当的参例上的一个方法。如果您有一个委托对象和一组适当的参数,则可以用这些参数调用该委托。数,则可以用这些参数调用该委托。
委托的一个有趣且有用的属性是,它不知道或不关心委托的一个有趣且有用的属性是,它不知道或不关心自己引用的对象的类。任何对象都可以;只是方法的参数自己引用的对象的类。任何对象都可以;只是方法的参数类型和返回类型必须与委托的参数类型和返回类型相匹配。类型和返回类型必须与委托的参数类型和返回类型相匹配。这使得委托完全适合“匿名”调用。这使得委托完全适合“匿名”调用。
案例案例:用运算符来组合委托:用运算符来组合委托
![Page 195: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/195.jpg)
目标目标:掌握委托的基本方法:掌握委托的基本方法步骤步骤::
11 、启动、启动 VS.NETVS.NET,新建一个控制台应用程序,名称填写为,新建一个控制台应用程序,名称填写为““ MyClassTest”MyClassTest” ,位置设置为“,位置设置为“ c:\CSharpSamples\cc:\CSharpSamples\chp4”hp4” 。。
22、在代码设计窗口中编辑、在代码设计窗口中编辑 Class1.csClass1.cs 。其中的代码编写如。其中的代码编写如下:下:
using System;using System;namespace MyClassTestnamespace MyClassTest{{
delegate void MyDelegate(string s);delegate void MyDelegate(string s);class MyClassclass MyClass{{
public static void Hello(string s)public static void Hello(string s)
![Page 196: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/196.jpg)
{ Console.WriteLine(" Hello, {0}!", s);{ Console.WriteLine(" Hello, {0}!", s);}}public static void Goodbye(string s)public static void Goodbye(string s){Console.WriteLine(" Goodbye, {Console.WriteLine(" Goodbye,
{0}!", s);{0}!", s);}}public static void Main()public static void Main(){{
MyDelegate a, b, c, d;MyDelegate a, b, c, d;// // 创建委托对象创建委托对象a = new MyDelegate(Hello);a = new MyDelegate(Hello);b = new MyDelegate(Goodbye);b = new MyDelegate(Goodbye);
![Page 197: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/197.jpg)
c = a + b;c = a + b;d = c - a;d = c - a;Console.WriteLine("Invoking deConsole.WriteLine("Invoking de
legate a:");legate a:");a("A");a("A");Console.WriteLine("Invoking deConsole.WriteLine("Invoking de
legate b:");legate b:");b("B");b("B");
Console.WriteLine("Invoking delegate c:");Console.WriteLine("Invoking delegate c:");c("C");c("C");Console.WriteLine("Invoking delegate d:");Console.WriteLine("Invoking delegate d:");d("D");d("D");
}}}}
}}
![Page 198: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/198.jpg)
33、按、按 Ctrl + F5Ctrl + F5编译并运行该程序,效果如图编译并运行该程序,效果如图 4-124-12所示。所示。
图 4-12 程序运行结果
![Page 199: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/199.jpg)
4.7.2 4.7.2 事件事件
![Page 200: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/200.jpg)
事件为类和类的实例提供了向外界发送通知的能力。事件为类和类的实例提供了向外界发送通知的能力。在在 CC 和和 C++C++ 中,没有事件的概念,它是在中,没有事件的概念,它是在 C#C#首次首次提出的。提出的。形象地说,事件形象地说,事件 (event)(event)就是类或对象用来“发出就是类或对象用来“发出通知”的成员。通过提供事件的句柄,客户能够把通知”的成员。通过提供事件的句柄,客户能够把事件和可执行代码联系在一起。事件和可执行代码联系在一起。11 、事件的声明、事件的声明 事件的声明分为两种,一种是事件域声明,一种事件的声明分为两种,一种是事件域声明,一种
是事件属性声明。是事件属性声明。 事件域声明的格式如下:事件域声明的格式如下: [[事件修饰符事件修饰符 ] event ] event 事件类型 事件名事件类型 事件名 ;; 事件属性声明的格式如下:事件属性声明的格式如下: [[事件修饰符事件修饰符 ] event ] event 事件类型 事件名 事件类型 事件名 {{访问器访问器 };};
![Page 201: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/201.jpg)
其中事件修饰符就是以前常提到的访问修饰符,如:其中事件修饰符就是以前常提到的访问修饰符,如:newnew 、、 publicpublic 、、 protectedprotected 、、 internalinternal 、、 privateprivate 、、staticstatic等。事件的类型等。事件的类型 (type)(type) 则必须是一个委托类则必须是一个委托类型,而此委托类型应预先声明。型,而此委托类型应预先声明。22、事件的预订和撤消、事件的预订和撤消 事件的预订是通过为事件加上左运算符“事件的预订是通过为事件加上左运算符“ +=”+=” 来来
实现的。实现的。OkButton.Click+=new EventHandler(OkButtonClOkButton.Click+=new EventHandler(OkButtonCl
ick);ick);这样,只要事件被触发,方法就会被调用。这样,只要事件被触发,方法就会被调用。事件的撤消则采用左操作符“事件的撤消则采用左操作符“ -=”:-=”:OkButton.Click-=new EventHandler(OkButtonCliOkButton.Click-=new EventHandler(OkButtonCli
ck);ck);
![Page 202: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/202.jpg)
我们举个例子,声明了一个使用我们举个例子,声明了一个使用 ButtonButton 类的登类的登录对话框类。对话框类含有两个按钮:录对话框类。对话框类含有两个按钮: OKOK和和 CancCancelel按钮。按钮。
程序清单:程序清单:public class LoginDialog: Formpublic class LoginDialog: Form{ Button OkButton;{ Button OkButton; Button CancelButton;Button CancelButton; public LoginDialog()public LoginDialog() { OkButton=new Button(…);{ OkButton=new Button(…); OkButton.Click+=new EventHandler(OkButtonClick);OkButton.Click+=new EventHandler(OkButtonClick); Cancel Button=new Button(…);Cancel Button=new Button(…); CancelButton.Click+=new EventHandler(CancelButtCancelButton.Click+=new EventHandler(CancelButt
onClick);onClick); }}
![Page 203: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/203.jpg)
void OkButtonClick(object sender, EventArgs e)void OkButtonClick(object sender, EventArgs e) { // { // 处理处理 OkButton.ClickOkButton.Click 事件事件 }} void CancelButtonClick(object sender, EventAr void CancelButtonClick(object sender, EventArgs e)gs e) { // { // 处理处理 CancelButton.ClickCancelButton.Click 事件事件 }}}} 如果在类中声明了事件,我们又希望像使用域如果在类中声明了事件,我们又希望像使用域
的方式那样使用事件,那么这个事件就不能是抽的方式那样使用事件,那么这个事件就不能是抽象的,也不能显式地包含事件访问声明。满足了象的,也不能显式地包含事件访问声明。满足了这两个条件后,在任何可以使用域的场合都同样这两个条件后,在任何可以使用域的场合都同样可以使用事件。可以使用事件。
![Page 204: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/204.jpg)
注意:对事件的触发相当十调用事件所表示的原型注意:对事件的触发相当十调用事件所表示的原型———— delegatedelegate 所以对所以对 delegatedelegate 型原型的调用必须型原型的调用必须先经过检查,确保先经过检查,确保 delegatedelegate 不是不是 nullnull 型的。型的。
33、事件访问器、事件访问器如上面的例子所示,大多数情况下事件的声如上面的例子所示,大多数情况下事件的声
明都省略了事件访问声明。什么情况下使用事件明都省略了事件访问声明。什么情况下使用事件访问声明呢?答案是:如果每个事件的存储开销访问声明呢?答案是:如果每个事件的存储开销太大,我们就可以在类中包含事件访问声明,按太大,我们就可以在类中包含事件访问声明,按私有成员的规则存放事件句柄列表。私有成员的规则存放事件句柄列表。
访问器的声明包括两种:添加访问器声明访问器的声明包括两种:添加访问器声明 (a(add-accessor-declaration)dd-accessor-declaration) 和删除访问器声明和删除访问器声明 (re(remove-accessor-declaration)move-accessor-declaration) 。。
![Page 205: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/205.jpg)
访问器声明之后跟随相关执行代码的语句块。在添访问器声明之后跟随相关执行代码的语句块。在添加访问器声明后的代码需要执行添加事件句柄的操加访问器声明后的代码需要执行添加事件句柄的操作,在删除访问器声明后的代码需要执行删除事件作,在删除访问器声明后的代码需要执行删除事件句柄的操作。不管是哪种事件访问器,都对应相应句柄的操作。不管是哪种事件访问器,都对应相应的一个方法,这个方法只有一个事件类型的值参数,的一个方法,这个方法只有一个事件类型的值参数,并且返回值为并且返回值为 voidvoid 。。
在执行预订操作时使用添加型访问器,在执在执行预订操作时使用添加型访问器,在执行撤消操作时使用删除型访问器。访问器行撤消操作时使用删除型访问器。访问器中实际上还包含了一个名为中实际上还包含了一个名为 valuevalue 的隐藏的隐藏的参数,因而访问器在使用局部变量时不的参数,因而访问器在使用局部变量时不能再使用这个名字。能再使用这个名字。
![Page 206: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/206.jpg)
下面给出了使用访问器的例子。下面给出了使用访问器的例子。程序清单:程序清单:
class Control: Componentclass Control: Component{{ //Unique keys for events//Unique keys for events static readonly object mouseDownEventKey=nestatic readonly object mouseDownEventKey=ne
w object();w object(); static readonly object mouseUpEventKey=new ostatic readonly object mouseUpEventKey=new o
bject();bject(); //Return event handler associated with key//Return event handler associated with key protected Delegate GetEventHandler(object keprotected Delegate GetEventHandler(object ke
y){…}y){…} //Add event handler associated with key//Add event handler associated with key
![Page 207: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/207.jpg)
protected void AddEventHandler(object key, Dprotected void AddEventHandler(object key, Delegate handler){…}elegate handler){…} //Remove event handler associated with key //Remove event handler associated with key protected void RemoveEventHandler(object protected void RemoveEventHandler(object key, Delegate handler){…}key, Delegate handler){…} // MouseDown // MouseDown 事件属性事件属性 public event MouseEventHandler MouseDopublic event MouseEventHandler MouseDownwn
{ add { AddEventHandler(mouseDownEventKey,{ add { AddEventHandler(mouseDownEventKey, value);} value);}
remove { AddEventHandler(mouseDownEveremove { AddEventHandler(mouseDownEventKey, value);}ntKey, value);}
}}
![Page 208: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/208.jpg)
//MouseUp //MouseUp 事件属性事件属性 public event MouseEventHandler MouseUppublic event MouseEventHandler MouseUp { add { AddEventHandler(mouseUpEventKey, { add { AddEventHandler(mouseUpEventKey, value);}value);} remove { AddEventHandler(mouseUpEventK remove { AddEventHandler(mouseUpEventKey, value);}ey, value);} } }}}44 、静态事件、静态事件 和域、方法等一样,在声明中使用了修饰符的事件称之为和域、方法等一样,在声明中使用了修饰符的事件称之为静态事件。静态事件不与具体的实例相关联,因此不能在静态事件。静态事件不与具体的实例相关联,因此不能在静态事件的访问器中引用静态事件的访问器中引用 thisthis关键字。此外,在静态事件关键字。此外,在静态事件声明时又加上声明时又加上 virtual, abstractvirtual, abstract 或或 overrideoverride修饰符也都是修饰符也都是不合法的。而对于非静态的事件,我们可以在事件的访问不合法的。而对于非静态的事件,我们可以在事件的访问器中使用器中使用 thisthis 来指代类的实例。来指代类的实例。
![Page 209: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/209.jpg)
实训四实训四
11 、定义一个类,是用域、属性和事件。、定义一个类,是用域、属性和事件。22、实验类成员的可访问性修饰符的作用。、实验类成员的可访问性修饰符的作用。33、创建一个接口,写出几个类实现这个接、创建一个接口,写出几个类实现这个接口,并互相调用。口,并互相调用。
44 、编写一个计算器程序,并处理除、编写一个计算器程序,并处理除 00异常。异常。55、自定义一个异常并捕获之。、自定义一个异常并捕获之。
![Page 210: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/210.jpg)
本章小结本章小结
C#C# 是一种彻底的面向对象语言,它把面是一种彻底的面向对象语言,它把面向对象的思想发挥到了极致。在上一章中,向对象的思想发挥到了极致。在上一章中,我们介绍了面向对象思想的基本概念,具我们介绍了面向对象思想的基本概念,具体讲述了体讲述了 C#C# 语言面向对象的支持。在这一语言面向对象的支持。在这一章中,我们更深入的介绍了章中,我们更深入的介绍了 C#C# 语言在面向语言在面向对象方面的支持细节,如:类的继承与多对象方面的支持细节,如:类的继承与多态、操作符重载、类型转换、结构、接口、态、操作符重载、类型转换、结构、接口、集合、索引器、异常处理、委托和事件等。集合、索引器、异常处理、委托和事件等。
![Page 211: 第 4 章 C# 面向对象高级编程](https://reader033.vdocuments.site/reader033/viewer/2022061402/56814937550346895db67936/html5/thumbnails/211.jpg)
练习与提高四练习与提高四11 、虚函数与抽象函数的作用和意义是什么?各用于那种情、虚函数与抽象函数的作用和意义是什么?各用于那种情
况?况?22、类成员有几种访问修饰符?各有什么特点和作用?、类成员有几种访问修饰符?各有什么特点和作用?33、简述多态的实现方法?、简述多态的实现方法?44 、事件与委托的关系是什么?、事件与委托的关系是什么?55、总结接口的意义,以及在什么情况下要使用接口?、总结接口的意义,以及在什么情况下要使用接口?66、指出抽象类与接口的相同与不同。、指出抽象类与接口的相同与不同。77、接口事件是什么?、接口事件是什么?88、在接口实现过程中需要注意那些因素?、在接口实现过程中需要注意那些因素?99、指出、指出 C#C# 中的各种异常类型和意义?中的各种异常类型和意义?1010、指出原来在、指出原来在 CC 中常用的异常处理方式?中常用的异常处理方式?1111 、写一个异常处理程序,并在、写一个异常处理程序,并在 FinallyFinally块中做退出操作。块中做退出操作。