《第04章 面向对象的高级编程.pdf》由会员分享,可在线阅读,更多相关《第04章 面向对象的高级编程.pdf(35页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、65第第4 4章章面向对象的高级编程面向对象的高级编程作者:猥琐的程序员作者:猥琐的程序员-铁匠铁匠腾讯微博:腾讯微博:http:/ 3 章介绍的基本内容之外,在 C#中还提供了很多其他的高级功能,比如反射、组合、多线程编程以及与非控代码的互操作等。本章主要介绍一些常用的面向对象的高级编程技术。4.1封装、继承与多态封装、继承与多态性是面向对象编程的三大原则。封装用于隐藏调用者不需要了解的信息;继承则简化了类的设计;多态性是指类为名称相同的方法提供不同实现方式的能力。在实际编程中,只有深刻理解这些概念,才能更好的利用面向对象技术编写出高质量的程序代码。4.1.1封装性在面向对象编程中,封装是指
2、把数据和处理这些数据的代码封装在一个类中,然后通过提供相应的属性和方法供调用者使用,通过隐藏调用者不需要的信息(如实现细节),可以让调用者只关心对象中对其有用的相关内容。在设计类时,应尽可能隐藏实现的细节,只提供给调用者需要知道的操作和数据。这样做的好处是当设计者修改实现的细节时,可以不影响调用者与类的交互方式。【例 4-1】通过属性进行封装。using System;using System.Collections.Generic;using System.Text;namespace EncapsulationExampleclass BankAccount/帐户余额private dec
3、imal accountBalance;public decimal AccountBalancegetreturn accountBalance;public BankAccount(decimal startAmount)accountBalance=startAmount;第 4 章面向对象的高级编程66/可以通过此方法取款public void Withdraw(decimal money)if(accountBalance=money)accountBalance-=money;class Programstatic void Main(string args)BankAccount
4、 ZhangSan=newBankAccount(1000);ZhangSan.Withdraw(100);Console.WriteLine(帐户的余额为0:C,ZhangSan.AccountBalance);Console.ReadLine();这是对银行账户余额 accountBalance 进行封装的一个例子。在这个例子中,银行帐户ZhangSan 可以通过 AccountBalance属性读取账户余额 accountBalance的值,但不能直接读取或修改私有成员 accountBalance的值。另外,由于 AccountBalance 属性只提供了 get 操作,因此也无法通过
5、该属性修改 accountBalance的值,从而避免了直接修改余额引起不正确的结果。4.1.2继承继承(Inheritance)是指类能够从它的父类中继承除构造函数以外的所有数据的定义和功能。继承能够提高代码的可重用性。通过继承,可以使程序员能够直接享用他人或自己事先写好的基类中已有的功能,而不必全部重新编写。在 C#中,作为基础的、被继承的类称为基类(Base Class),继承自别的类的子类称为扩充类(Derived Class,又叫派生类)。C#语言提供了两种实现继承的方式:类继承和接口继承。不过类继承只允许单一继承,即只有一个基类。单一继承已经能够满足大多数面向对象应用程序开发上的要
6、求,也有效的降低了复杂性。如果必须使用多重继承,可以通过接口来实现。注意,虽然继承是非常有用的编程概念,但使用不当也会带来一些负面的效果。下列情况下可以使用类继承:1)扩充类与基类的关系是“属于”关系而不是“具有”关系,或者说,扩充类不能是基类的“子集”,也不能是只包含基类中的一部分。“具有”关系的类不适合使用类继承,因为这样可能会继承不适当的属性和方法。图 4-1 说明了“属于”关系和“具有”关系的区别。2)可以重用基类的代码。例如,如果一个数据库中有多个表,对每一个表都设计添加、删除、修改等图4-1关系比较图第 4 章面向对象的高级编程67功能显然既费时又容易出错,这时使用类继承就是比较好
7、的选择。3)需要将相同的类和方法应用到不同的数据类型。这时可以利用重写基类中的某些方法来实现。4)类层次分级比较少,而且其他开发人员不可能添加太多的级别。继承最适合于分级相对较少的类层次结构。一般来说,应将层次结构限制在低于六级。5)需要只修改基类就可以对继承的类进行全部更改的情况。继承的一个最强大的功能是在基类中进行的更改将自动传播到派生类中。例如更新一个方法的实现,从而几十甚至上百个派生类都可以自动使用该新代码。但是,一般情况下,应避免更改基类成员的名称或类型,因为这样容易导致使用原成员的扩充类出现问题。1.扩充类要声明一个扩充类,可以使用下面的语法:访问修饰符访问修饰符 classcla
8、ssclassclass 扩充类名称:基类名称扩充类名称:基类名称 /程序代码程序代码 扩充类继承了所有定义在基类中数据的定义和方法。但是要注意,构造函数则排除在外,不会被继承下来,而且扩充类不继承基类的数据值。【例 4-2】类和继承。using System;namespace InheritanceExamplepublic class Animalpublic Animal()Console.WriteLine(Hello,Animal!);public Animal(string name)Console.WriteLine(Hello,My name is +name+!);publ
9、ic void Eat()Console.WriteLine(Eating);public class Cat:Animalpublic Cat()Console.WriteLine(Hello,Cat!);class Programstatic void Main(string args)第 4 章面向对象的高级编程68Cate=new Cat();e.Eat();Console.ReadLine();类 Cat 继承自类 Animal,当创建 Cat 的实例时,会首先调用基类的构造函数,然后调用扩充类的构造函数,因此输出结果为:Hello,Animal!Hello,Cat!Eating不过
10、,扩充类并不一定能够直接使用基类中所定义的所有数据与方法,如基类的 public成员将会成为扩充类的 public 成员,public 表示允许外部类自由地调用;而基类的 private 成员则只会被基类本身的成员存取,扩充类无法使用基类的 private 成员。2.密封类密封类是指不能被其他类继承的类。在 C#中,使用 sealed 关键字声明密封类。由于密封类不能被其他类继承,因此系统在运行时就可以对密封类的方法调用进行优化,从而提高系统的性能。同样,sealed 关键字也可以限制基类中的方法被扩充类重写。例如,下面的程序代码是错误的。【例 4-3】错误的用法。using System;n
11、amespace SealExamplepublic class Hellopublic sealed void SayHello()Console.WriteLine(这是基类!);public class NewHello:Hellopublic new void SayHello()Console.WriteLine(这是扩充类!);class Programstatic void Main(string args)NewHello me=new NewHello();me.SayHello();Console.ReadLine();第 4 章面向对象的高级编程69因为 SayHello
12、 已经用 sealed 限制为不能被继承,所以在 NewHello 类中试图重写 SayHello方法是错误的。4.1.3多态性多态性是指类为名称相同的方法提供不同实现方式的能力。利用多态性,就可以调用类中的某个方法而无需考虑该方法是如何实现的。有以下几种实现多态性的方式。第一种方式是通过继承实现多态性。多个类可以继承自同一个类,每个扩充类又可根据需要重写基成员以提供不同的功能。第二种方式是通过抽象类实现多态性。抽象类本身不能被实例化,只能在扩充类中通过继承使用。抽象类的部分或全部成员不一定都要实现,但是要在继承类中全部实现。抽象类中已实现的成员仍可以被重写,并且继承类仍可以实现其他功能。第三
13、种方式是通过接口实现多态性。多个类可实现相同的“接口”,而单个类可以实现一个或多个接口。接口本质上是类需要如何响应的定义。接口仅声明类需要实现的方法、属性和事件,以及每个成员需要接收和返回的参数类型,而这些成员的特定实现留给实现类去完成。1.虚拟方法如果基类提供的功能不能够满足要求,而且基类允许重写,则可以在扩充类中重新定义基类的方法。在基类中,如果想让某个方法或者事件被扩充类重写,可以使用修饰符 virtual 表明,例如:public virtual void myMethod()/程序代码这样,在扩充类中就可以使用修饰符 override 重写该方法或者事件了。例如:public ove
14、rride void myMethod()/程序代码在 C#中,定义的方法默认都是非虚拟的(non-virtual),即不允许重写这些方法,但是基类中的方法使用了 virtual 修饰符以后,该方法就变成了虚拟方法。在扩充类中,既可以重写基类的虚拟方法,也可以不重写。但是要注意,如果重写基类的虚拟方法,必须在扩充类中用override 关键字声明。使用虚拟方法与重写方法时,需要注意下面几个方面:1)虚拟方法不能声明为静态(static)的。因为静态的方法是应用在类这一层次的,而面向对象的多态性只能在对象上运作,所以无法在类中使用。2)virtual 不能和 private 一起使用。因为声明为
15、 private 就无法在扩充类中重写了。3)重写方法的名称、参数个数、类型以及返回值都必须和虚拟方法的一致。【例 4-4】重写基类的方法。using System;namespace OverrideExampleclass Shape第 4 章面向对象的高级编程70public virtual void ShowShape()Console.WriteLine(我是图形!);class Triangle:Shapepublic override void ShowShape()base.ShowShape();Console.WriteLine(我是三角形!);class Programs
16、tatic void Main(string args)Triangle me=new Triangle();me.ShowShape();Console.ReadLine();输出结果:我是图形!我是三角形!在类 Triangle 中使用 base.ShowShape()是指调用在类 Shape 中声明的 ShowShape 方法。基访问禁用了虚拟调用机制,它只是简单地将那个重写了的基方法视为非虚拟方法。注意,只有使用 override 修饰符时,才能重写基类的方法。否则,在继承的类中声明一个与基类方法同名的方法会隐藏基类的方法。【例 4-5】容易引起混乱的方法声明。using System
17、;namespace ConfusionExampleclass Shapepublic virtual void F()class Triangle:Shapepublic virtual void F()class Programstatic void Main(string args)Triangle me=new Triangle();me.F();第 4 章面向对象的高级编程71Console.ReadLine();由于 Triangle 中的 F 方法不包含 override 修饰符,因此 Triangle 中的 F 方法不重写 Shape中的 F 方法,而是隐藏 Shape 中同
18、名的 F 方法,但是由于在 Triangle 中没有显式的声明隐藏基类的 F 方法,因此编译后在 Triangle 中的 F 方法名下面会出现一条下划线,当鼠标停留到Triangle 中的 F 方法名处,会显示一条警告信息。2.隐藏基类的方法在扩充类中,可以使用 new 关键字来隐藏基类的方法,即使用一个完全不同的方法取代旧的方法。与方法重写不同的是,使用 new 关键字时并不要求基类中的方法声明为 virtual,只要在扩充类的方法前声明为 new,就可以隐藏基类的方法。【例 4-6】隐藏基类的方法。using System;namespace ConcealExamplepublic cl
19、ass Hellopublic void SayHello()Console.WriteLine(这是基类!);public class NewHello:Hellopublic new void SayHello()Console.WriteLine(这是扩充类!);class Programstatic void Main(string args)Hello b=new Hello();b.SayHello();NewHello d=new NewHello();d.SayHello();Console.ReadLine();输出结果:这是基类!这是扩充类!什么情况下需要这样做呢?比如现在
20、根据需要要求设计人员 A 重新设计基类中的某个方第 4 章面向对象的高级编程72法,该基类是一年前由另一组设计人员设计的,并且已经交给用户使用,可是原来的设计人员在该方法前并没有加 virtual 关键字,设计人员 A 也没有这些源代码。这种情况下显然既不能使用 override 重写基类的方法,又无法直接修改基类的方法,这时就需要隐藏基类的方法。3.抽象类抽象类使用 abstract 修饰符,用于表示所修饰的类是不完整的,即类中的成员(例如方法)不一定全部实现,可以只有声明没有实现。抽象类只能用作基类。抽象类与非抽象类相比有以下主要不同之处:第一是抽象类不能直接被实例化,只能在扩充类中通过继
21、承使用,对抽象类使用 new 运算符会产生编译时错误。第二个不同点是抽象类可以包含抽象成员,而非抽象类不能包含抽象成员。当从抽象类派生非抽象类时,这些非抽象类必须具体实现所继承的所有抽象成员。例如:abstract class Apublic abstract void F();/抽象方法,注意该方法没有实现部分abstract class B:Apublic void G()class C:Bpublic override void F()/实现部分在上面的代码中,抽象类 A 引入抽象方法 F,注意,抽象方法均为虚拟方法,不需要加virtual。类 B 引入另一个方法 G,但由于它不提供 F
22、 的实现,因此 B也必须声明为抽象类。类C 重写了抽象方法 F,并提供了一个具体实现。由于 C 中没有抽象成员,因此可以将 C 声明为非抽象类。在非抽象类中实现抽象类时,必须实现抽象类中的每一个抽象方法,而且每个实现的方法必须和抽象类中指定的方法一样,即接收相同数目和类型的参数,具有同样的返回值。【例 4-7】抽象方法和抽象类。using System;namespace AbstractExamplepublic abstract class Shapepublic virtual void Draw()Console.WriteLine(画一种图形!);public abstract vo
23、id Rotate();public class Square:Shape第 4 章面向对象的高级编程73public override void Draw()Console.WriteLine(画一个正方形!);public override void Rotate()Console.WriteLine(顺时针方向旋转正方形!);class Programstatic void Main(string args)Square me=new Square();me.Draw();me.Rotate();Console.ReadLine();输出结果:画一个正方形!顺时针方向旋转正方形!如果有一
24、个通用方法,该方法对所有扩充类来说是公共的,并且强制要求所有扩充类都必须实现这个方法,这种情况下就可以把该方法定义为基类中的抽象方法。4.2版本控制用 C#编写方法时,如果在扩充类中重写基类的方法,需要用 override 声明;要隐藏基类的方法,需要用 new 声明,这就是 C#进行版本控制的依据。在 C#中,所有的方法默认都是非虚拟的,调用非虚拟方法时不会受到版本的影响,不管是调用基类的方法还是调用扩充类的方法,都会和设计者预期的结果一样执行实现的程序代码。相比之下,虚拟方法的实现部分可能会因扩充类的重写而影响执行结果。也就是说,在执行时期调用虚拟方法时,它会自动判断应该调用哪个方法。比如
25、,如果基类中声明一个虚拟方法,而扩充类的方法中使用了 override 关键字,则执行时会调用扩充类的方法;如果扩充类的方法没有使用 override 关键字,则调用基类的方法。而没有声明为 virtual 的非虚拟方法,则在编译时就已经确定了应该调用哪个方法了。【例 4-8】版本控制使用 new 修饰符。using System;namespace NewExampleclass Apublic void Method()Console.WriteLine(A.Method);第 4 章面向对象的高级编程74class B:Apublic new void Method()Console.W
26、riteLine(B.Method);class Programstatic void Main(string args)Aa=new A();B b=new B();Ac=b;a.Method();b.Method();c.Method();/按回车键结束Console.ReadLine();输出结果:A.MethodB.MethodA.Method例子中先建立了 A 类的对象 a,因此可以直接调用 Method 方法,并输出相关的字符串。b 对象声明为 B 类型,当调用 b 的 Method 方法时,实际上是调用定义在 B 类中的方法。c 对象声明为 A 类型,却初始化为 B类的对象。当调
27、用 c的 Method 方法时,由于 c 对象声明为 A 类型,因此 c 便被视为 A 类型的对象,调用的将是 A 类中的 Method 方法。C#在执行时期调用声明为 virtual 的虚拟方法时,会动态的决定要调用的方法是定义在基类的方法,还是定义在扩充类中的方法。实际上是根据下面的原则来判断的:调用继承的最后实现(most derived implementation)部分的方法。【例 4-9】使用 virtual 与 new 进行版本控制。using System;namespace VirtualandNewExampleclass Apublic virtual void Meth
28、od()Console.WriteLine(A.Method);class B:A第 4 章面向对象的高级编程75public new virtual void Method()Console.WriteLine(B.Method);class Programstatic void Main(string args)Aa=new A();B b=new B();Ac=b;a.Method();b.Method();c.Method();Console.ReadLine();输出结果:A.MethodB.MethodA.Method在这个例子中,c 对象仍然声明为 A 类,并初始化为 B 类的对
29、象,当调用 c的 Method 方法时,C#会先检查 A 类的 Method 方法,然后发现 Method 方法使用 virtual 关键字,于是进一步检查 B 类,而 B 类的 Method 方法使用了 new 关键字,表示这是一个全新的方法,虽然 B类的 Method 方法也使用了 virtual 关键字,但是 B 类的 Method 方法和 A 类的 Method 方法没有重写关系,只是和基类的 Method 方法同名罢了,因此,最后被调用的是 A 类的 Method 方法。【例 4-10】使用 virtual、new 与 override 进行版本控制。using System;nam
30、espace VirtualNewOverrideExampleclass Apublic virtual void Method()Console.WriteLine(A.Method);class B:Apublic new virtual void Method()Console.WriteLine(B.Method);class C:B第 4 章面向对象的高级编程76public override void Method()Console.WriteLine(C.Method);class Programstatic void Main(string args)Aa=new A();B
31、 b=new C();Ac=b;a.Method();b.Method();c.Method();Console.ReadLine();输出结果:A.MethodC.MethodA.Method在这个例子中,b 对象仍然声明为 B 类,但初始化为 C 类的对象,当调用 b 的 Method 方法时,C#会先检查 B 类的 Method 方法,发现 Method 方法使用了 new 修饰符,说明 B 类的Method 方法隐藏了 A 类的 Method 方法,但是由于 B 类的 Method 方法使用了 virtual 修饰符,并且 b 被初始化为 C 类的对象,于是进一步检查 C 类的 Met
32、hod 方法,C 类的 Method 方法使用 override 关键字,表示重写了 B 类的 Method 方法,根据调用继承的最后实现部分的方法的原则,因此最后被调用的是 C 类的 Method 方法。c 对象声明为 A 类,但是却被初始化为 B类的对象,当调用 c的 Method 方法时,C#会先检查 A 类的 Method 方法,发现 A 类的 Method 方法使用了 virtual 关键字,于是进一步检查 B类的 Method 方法,由于 B 类的 Method 方法使用了 new 修饰符,说明 B 类的 Method 方法并没有继承 A 类的 Method 方法,因此最后被调用的
33、是 A 类的 Method 方法。4.3接口在某种程度上,接口像一个抽象类。与抽象类不同的是,接口是完全抽象的成员集合。接口的主要特点是只有声明部分,而没有实现部分。和类一样,接口也定义了一系列属性、方法和事件等。但与类不同的是,接口本身并不提供接口成员的实现。而是在继承接口的类中实现,并在类中被定义为单独的实体。接口中不包含任何程序代码,例如下面的写法是错误的:public interface Itestint sum()第 4 章面向对象的高级编程77/代码定义在接口中的方法都是 public 的,不能再声明。例如下面的写法也是错误的:public interface Itestpubli
34、c int sum();/不能有 public 声明接口表示调用者和设计者的一种约定,比如提供的某个方法用什么名字、需要哪些参数、每个参数的类型是什么等。在多人合作开发同一个项目时,事先定义好相互调用的接口可以大大提高开发的效率。接口是用类来实现的,实现接口的类必须严格按照接口的声明来实现接口提供的功能。有了接口,就可以在不影响现有接口声明的情况下,修改接口的内部实现,从而使兼容性问题最小化。当其他设计者调用了声明的接口后,就不能再随意更改接口的定义,否则项目开发者事先的约定就失去了意义。但是可以在类中修改相应的代码,完成需要改动的内容,而提供的接口则保持不变。抽象类和接口的一个主要差别是:类
35、可以继承自多个接口,但仅能从一个抽象类或任何其他类型的单个类继承。选择将功能设计为接口还是抽象类有时是一件困难的事。抽象类是一种不能实例化而必须从中继承的类。抽象类可以完全实现,但更常见的是部分实现或者根本不实现,从而封装继承类的通用功能。使用接口还是抽象类来为组件提供多态性主要考虑以下几个方面:1)如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单易行的方法来控制组件版本。通过更新基类,使所有继承类都自动更新。另一方面,为了保护为使用接口而编写的现有系统,要求接口一旦创建就不能更改。如果需要接口的新版本,必须创建一个全新的接口。2)如果创建的功能将在大范围的完全不同的对象间使用,则使
36、用接口。抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。3)如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。设计优良的接口往往很小且相互独立,减少了发生性能问题的可能。4)如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽象类允许部分实现类,而接口不包含任何成员的实现。4.3.1接口的声明与实现在 C#中,使用 interface关键字声明一个接口。常用的语法是:访问修饰符访问修饰符 interfaceinterfaceinterfaceinterface 接口名称接口名称 /接口体接口体 一般情况下,建议以大写的“I”开头指定接
37、口名,表明这是一个接口。要实现一个接口,必须要有相应的类。实现某个接口的任何类都将拥有该接口中的所有元素。因此,当需要在不相关的类中实现同样的功能时,就可以使用接口。第 4 章面向对象的高级编程78【例 4-11】接口的声明与实现。using System;namespace InterfaceExample1interface Ifunction1int sum(int x1,int x2);interface Ifunction2string str get;set;class MyTest:Ifunction1,Ifunction2/此处的冒号表示接口的实现private string
38、mystr;/构造函数public MyTest()/构造函数public MyTest(string str)mystr=str;/实现接口 Ifunction1 中的方法public int sum(int x1,int x2)returnx1+x2;/实现接口 Ifunction2 中的属性public string strgetreturn mystr;setmystr=value;class Programstatic void Main(string args)/直接访问实例MyTesta=new MyTest();Console.WriteLine(a.sum(10,20);My
39、Test b=new MyTest(How are you);Console.WriteLine(b.str);/使用接口第 4 章面向对象的高级编程79Ifunction1 f1=(Ifunction1)a;Console.WriteLine(f1.sum(20,30);Ifunction2 f2=(Ifunction2)b;Console.WriteLine(f2.str);/按回车键结束Console.ReadLine();输出结果:30Howare you!50Howare you!4.3.2显式方式实现接口由于不同接口中的方法可以重名,因此在一个类中实现接口中的方法时就存在着多义性的
40、问题,对于这类问题,可以显式实现接口中的方法。对于显式实现的方法,不能通过类的实例进行访问,而必须使用接口的实例。【例 4-12】以显式方式实现接口。using System;namespace InterfaceExample2interface Ifunctionint sum(int x1,int x2);class MyTest:Ifunction/实现接口 Ifunction1 中的方法int Ifunction.sum(int x1,int x2)returnx1+x2;class Programstatic void Main(string args)/下面注释掉的两行代码为错误
41、的访问例子,如果这样写,会提示“MyTest 不包/含对 sum 的定义”的错误。这是因为 sum 是显式实现接口,只能通过接口调用。/MyTest a=new MyTest();/Console.WriteLine(a.sum(10,20);/通过接口访问实例MyTest myTest=new MyTest();Ifunction b=(Ifunction)myTest;Console.WriteLine(b.sum(20,30);Console.ReadLine();第 4 章面向对象的高级编程80输出结果:504.3.3通过接口实现多继承在继承时,C#只允许有一个被继承的类,但是可以通过
42、接口实现多继承。【例 4-13】通过接口实现多继承。using System;using System.Collections.Generic;using System.Text;namespace InterfaceExample3class MyBaseClass1public int add(intx1,int x2)returnx1+x2;interface IBasefunctionint Multiply(int x1,int x2);class MyBaseClass2:IBasefunctionpublic int Subtract(int x1,int x2)returnx1
43、-x2;/显式实现接口 IBasefunction 中的方法int IBasefunction.Multiply(intx1,int x2)returnx1*x2;interface Ifunction1int add(intx1,int x2);interface Ifunction2int Subtract(int x1,int x2);/通过接口实现多继承class MyClass:MyBaseClass2,Ifunction1,Ifunction2/实现接口 Ifunction1 中的方法int Ifunction1.add(intx1,int x2)第 4 章面向对象的高级编程81M
44、yBaseClass1 class1=new MyBaseClass1();return class1.add(x1,x2);/实现接口 Ifunction2 中的方法int Ifunction2.Subtract(int x1,int x2)MyBaseClass2 class2=new MyBaseClass2();return class2.Subtract(x1,x2);/增加的新方法public void Hello()Console.WriteLine(Hello);class Programstatic void Main()MyClass myClass=new MyClass
45、();Ifunction1 f1=(Ifunction1)myClass;Console.WriteLine(f1.add(5,2);Ifunction2 f2=(Ifunction2)myClass;Console.WriteLine(f2.Subtract(5,2);IBasefunction f3=(IBasefunction)myClass;Console.WriteLine(f3.Multiply(5,2);myClass.Hello();/按回车键结束Console.ReadLine();输出结果:7310Hello4.4委托委托(delegate)是一种数据结构,提供类似 C+中
46、函数指针的功能,不同的是 C+的函数指针只能够指向静态的方法,而委托除了可以指向静态的方法之外,还可以指向对象实例的方法。另外,delegate 是完全的面向对象且使用安全的类型。编程人员可以利用 delegate 在执行时期传入方法的名称,动态的决定欲调用的方法。委托的最大特点是,它不知道或不关心自己引用的对象的类。任何对象中的方法都可以通过委托动态的调用,只是方法的参数类型和返回类型必须与委托的参数类型和返回类型相匹配。委托主要用在两个方面:其一是 CallBack(回调)机制;其二是事件处理。第 4 章面向对象的高级编程82建立和使用 delegate 类型可按照下面的步骤进行,例 4-
47、14 给出了完整的程序代码。1)声明样板。首先要声明一个 delegate 类型:public delegate string MyDelegate(string name);代码中先定义一个 delegate 类型,名为 MyDelegate,它包含一个 string 类型的传入参数name,一个 string 类型的返回值。当 C#编译器编译这行代码时,会生成一个新的类,该类继承自 System.Delegate 类,而类的名称为 MyDelegate。从语法形式上看,定义一个委托非常类似于定义一个方法。即:访问修饰符访问修饰符 delegatedelegatedelegatedelega
48、te 类型类型 委托名(参数序列)委托名(参数序列);但是,方法有方法体,而委托没有方法体。因为它执行的方法是在使用委托时动态指定的。2)定义准备调用的方法。由于这个方法是通过 delegate 调用的,因此,此方法的参数类型、个数以及参数的顺序都必须和 delegate 类型相同。例 4-14 中定义了两个方法:FunctionA 与 FunctionB。这两个方法的参数和 MyDelegate 的类型一样,有一个 string 类型的传入参数,有一个 string 类型的返回值:public static string FunctionA(stringname)public static
49、string FunctionB(string name)3)定义 delegate 类型的处理函数,并在此函数中通过 delegate 类型调用定义的方法。在这个例子中,处理函数的功能比较简单,仅仅输出一个字符串,字符串中包含通过MyDelegate 类型调用的方法得到输出的内容。public static void MethodA(MyDelegate Me)Console.WriteLine(Me(张三);由于 MyDelegate 类型的定义中有一个 string 类型的传入参数,所以使用时也必须传入一个字符串,即:Me(张三)因此,如果 Me 指向的是 FunctionA,则会执行
50、FunctionA 内的程序代码,如果 Me 指向的是 FunctionB,则会执行 FunctionB 内的程序代码。4)创建实例,传入准备调用的方法名。由于声明一个 delegate 类型在编译时期会被转换成一个继承自 System.Delegate 的类,因此要使用 delegate 类型时,必须先建立 delegate 的实例,并把它关联到一个方法:MyDelegate a=new MyDelegate(FunctionA);本行代码的含义是:a 指向 FunctionA 方法的程序代码段。注意,创建 Delegate 的实例时,只需要指定调用的方法名,不能指定方法需要的参数。建立 d