第5章 继承性、多态性和命名空间编程
• 继承性(inheritance)和多态性(polymorphism)是面向对象的重要机制,其提升了软件模块的可复用性和可扩充性。在第4章中已经介绍了面向对象的另外一特性继承性。这一章咱们将介绍了两种C# 2008中的重要概念抽象类和密封类,它们显示了C#语言的多态性。
• C# 2008程序是经过使用名空间来组织的。其不只能够是应用程序的内部结构体系,一样也能够是外部结构体系。若是程序中的一些元素要被导出到其余程序,可使用命名空间。程序中免不了会出现错误,这时就会用到C#语言的异常处理机制。
5.1 继承机制
• 面向对象的重要机制之一继承是可使用之前建造类的方法和属性。经过简单的程序代码来建造功能强大的类,不只会节省不少编程时间,并且还能够减小代码出错的机会。
5.1.1 继承的概念
• 经过一个实际应用问题,来说解类的继承这个问题,如代码所示。
• 【本示例参考:\示例代码\Chap05\NoUseInheritance】
• 在上面的程序中,定义了Person类和Student类,结果发现Student类包含了Person类的全部属性和方法。在C# 2008语言中,只要代表Student类继承了Person类,就不用在类Student中重复写类Person中的代码了。
5.1.2 继承的机制
• 子类对象中的成员变量的初始化实过程以下:
• (1)分配成员变量的存储空间,并进行默认的初始化。
• (2)绑定构造函数参数,就是把new Person(实际参数列表)中所传递进的参数赋值给构造函数中的形式参数变量。
• (3)若是有this()调用,则调用相应的重载构造方法。
• (4)显式或隐式追溯调用父类的构造方法。
• (5)进行实例变量的显式初始化操做,也就是执行定义成员变量时就进行赋值的语句。
• (6)执行当前构造函数体中的代码。
5.2 多态性
• 在面向对象的系统中,多态性容许对一个对象进行操做,由一个对象完成一系列的动做,具体实现哪一个动做由系统负责解释。在C# 2008中,多态性的定义是:同一操做做用于不一样的类的实例,不一样类将进行不一样的解释,最后产生不一样的执行结果。其支持两种类型的多态性:编译时多态性和运行时多态性。编译时多态性是经过重载来实现的,其根据传递的参数、返回的类型等信息决定实现何种操做。在第4章已经介绍了。运行时多态性是指直到系统运行时,才根据实际状况决定实现何种操做,其是经过虚方法来实现的。
5.2.1 虚方法
• 虚方法是经过在方法声明语句的访问修饰符和返回类型之间放置virtual关键字来实现的。当调用虚方法时,运行将肯定调用对象是什么类的实例,并调用适当的覆盖方法,经过override关键字来覆盖。经过一段简单的代码如所示,来讲明虚方法跟非虚方法的区别。
• 【本示例参考:\示例代码\Chap05\Difference】
• 运行结果:
• A.F
• B.F
• B.G
• B.G
5.2.2 抽象类和抽象方法
• 抽象方法能够当作是没有方法体的虚方法。其是必须被派生类覆盖的方法。在C# 2008中是经过关键字abstract来实现的。若是类的任何一个方法都是抽象的,则该类也必须声明为抽象的。
• 抽象类的用途是提供多个派生类可共享的基类的公共定义,并使用abstract关键字定义。先定义一个抽象类:
• abstract class MyClass
• {
• }
• 抽象类能够当作是接口和普通类的结合,代码5-6演示了抽象方法的使用。
5.2.3 抽象方法
• 抽象类中能够定义抽象方法,若是一个方法要声明为抽象方法,则方法前加上abstract修饰符便可。抽象方法是一个新的虚方法,它不提供具体的方法实现代码。只能在抽象类中声明抽象方法,对抽象方法,不能使用static或virtual修饰符,并且方法中不能有任何可执行代码,只要给出方法的原型就能够了。抽象类的派生类必须实现全部抽象方法,如代码所示。
• 【本示例参考:\示例代码\Chap05\AbstractMethod2】
5.2.4 密封类和密封方法
• 与override关键字连用的还有sealed关键字,sealed关键字用来表示密封的意思。在C# 2008中,密封类的做用是限制扩展性和灵活性。先定义一个简单的密封类:
• sealed class MyClass
• {
• }
• 当程序中密封了某个类时,其余类是不能继承该类的,如代码所示。
• 【本示例参考:\示例代码\Chap05\SealedClass1】
5.2.5 方法的隐藏
• 【本节示例参考:\示例代码\Chap05\HidingMethod】
• 还有一种方法能够实如今派生类中的覆盖,即new关键字。这种过程叫作方法的隐藏。可是,派生类和非抽象类的基类方法必须有相同的方法。代码演示了如何实现方法的隐藏。
• 运行结果:
• Study方法被调用
• Person方法被调用
• 方法隐藏的做用就是能够改变基类的方法。若是一个派生类型被强制向上转换为基类型,基类型的方法将被调用。
5.3 类型的转换
• 第3章讲述了基本数据类型变量的类型转换问题,其实对象类型转换也差很少是一个道理。不过在处理对象以前,有时候还常常对变量的类型进行判断,因此下面从类型的判断开始。
5.3.1 is关键字
• is关键字能够检查对象是否与特定的类型兼容。显然,能够用它来判断对象是否为给定的类型。定义格式为:
• operand is type
• 在上述定义中,当type是一个类,而operand也是该类型、或继承了该类型、或封箱到该类型中时结果为true;当type是一个接口类型,而operand也是该类型,或者执行该接口的类型结果也为true;当type是一个值类型,而operand也是该类型,或者被拆箱到该类型中时结果也为true。以下面的一个简事例:
• If (newClass is object)
• {
• 执行相应的操做;
• }
• else
• {
• 执行相应的操做;
• }
5.3.2 转换机制
• 值类型和引用类型的根本区别在于在内存中的存储方式,值类型老是在内存中的栈中存储,而引用类型倒是在堆栈中存储。堆栈与栈的区别在于,当定义一个值类型变量时,会在栈中分配适当大小的内存,内存中的这个空间用来存储变量所含的值。引用变量也利用栈,但这时栈包含的知识对另外一个内存位置的引用,而不是实际值。这个位置是堆中的一个地址。
• 值类型表明基本数据类型,分为三种简单类型(基本数据类型)、结构(用户定义的值类型)和枚举。而引用类型分为类、接口、数组和委托。在第2章已经介绍了值类型间的显示转换和隐式转换。这一小节中,将介绍引用类型间的转换。代码5-15演示了如何实现子类转化为父类。
• 【本示例参考:\示例代码\Chap05\Transformation1】
5.3.3 as关键字
• as关键字用于在兼容的引用类型之间执行转换,把一种类型转换为指定的引用类型。与强制转换不一样as关键字并不会引起错误。其基本格式为:
• operand as type
• 当operand的类型是type类型、operand的类型能够隐式转换为type类型、operand的类型能够封箱到类型type类型时,operand的类型就会被转换为相应type类型,不然operand的类型会被赋予null。代码演示了as关键字的使用。
• 注意:若是operand的类型能够显示转换为type,则operand的类型的结果就是null。
• 【本示例参考:\示例代码\Chap05\TestAs】
5.3.4 封箱(boxing)和拆箱(unboxing)
• 第2章讨论了值类型间的转换,而上面的小节讨论了引用类型间的转换,同时介绍了二者间的区别。封箱(boxing)和拆箱(unboxing),就是在任何值类型、引用类型和object类型间进行转换。
• 1.封箱(boxing)
• 封箱是把值类型转换为System.Object类型,或者转换为由值类型执行的接口类型。当把一个值类型转换一个System.Object类型,也就是建立一个System.Object类型实例并将这个值复制给这个object。
• 2.拆箱(unboxing)
• 和封箱转换正好相反,拆箱转换是将一个对象类型显示的转换成一个值类型,或是将一个接口类型显示的转换成一个执行该接口的值类型,这种转换是显示进行的,其语法相似于前面的显示类型转换。拆箱的过程分为两步,首先检查这个对象实例。而后把这个实例的值复制给值类型的变量。
5.4 异常处理
• C# 2008语言支持错误处理和异常(exception)。异常处理是捕获预期的和突发事件的技术,其目的是在错误发生以前,可以事先预料错误,使程序更可靠。异常定义了程序中遇到的非致命的错误,而不是编译时的语法错误。
5.4.1 异常处理基础
• 异经常使用来表示在应用程序执行期间执行间的错误,以及其余意外行为。当遇到下列状况时,可能会引起异常:
• 操做资源不可用。
• 公共语言运行库遇到意外状况。
• 自定义抛出异常。
• 在.NET Framework中,类Exception类表示基类异常,其余异常从其继承而来。该类经常使用的属性有StackTrace属性,InnerException属性,Message属性,HelpLink属性,Data属性,Source属性和TargetSite属性。
5.4.2 异常的捕获
• 理解了异常就能够捕获异常,代码演示了如何实现异常的捕获。
• 【本示例参考:\示例代码\Chap05\TestException】
• 代码异常捕获:TestException
• using System;
• using System.Collections.Generic;
• using System.Linq;
• using System.Text;
•
• namespace TestException
• {
• class Program
• {
• static void Main(string[] args)
• {
• int[] myArray = new int[4] { 1, 2, 3, 4 };
• //错误的赋值
• myArray[5] = 5;
• }
• }
• }
5.4.3 throws关键字
• 在异常处理中,还有一个throws关键字,其做用也很是重要。为了详细的了解该关键字,请从代码开始讲解。
• 【本节示例参考:\示例代码\Chap05\TestThrows1】
• 在上述的例子中,假设TestException类和MyException类不是同一我的写的,写TestException类的人,在main方法中调用MyException类的数组时候,如何知道数组会出现异常状况呢?也只有知道了它会发生错误,才可以想到用try……catch语句去处理可能发生的异常。
5.4.4 finally关键字
• 【本节示例参考:\示例代码\Chap05\TestFinally】。
• 在try……catch语句后,还能够有一个finally语句,finally语句中的代码块无论异常是否被捕获老是要执行的,代码演示了该关键字的使用。
• 运行结果:
• 出现错误了
• 程序执行完毕
5.4.5 异常的使用细节
• 当使用异常的时候,必须注意:
• 一个方法被覆盖时,覆盖它的方法必须抛出异常或异常的子类。
• 若是父类抛出多个异常,那么覆盖方法必须抛出那些异常的一个子集,也就是说,不能抛出新的异常。
5.5 命名空间
• 微软公司为程序开发人远提供了成千上万的类组成类库,若是不对这些类进行分门别类的使用和存放,就会像开发人员不用文件夹去管理众多的文件同样,在使用的时候不只极度困难和不方便,也极易出现类的命名冲突问题。在C# 2008中是经过引入命令空间机制,提供类的多层类命名空间,来解决上述问题。
5.5.1 声明命名空间
• 【本节示例参考:\示例代码\Chap05\Testnamespace】
• 命名空间是C#类、接口、委托、枚举和其余类型的一个逻辑上的组合。其不只在使用上灵活了不少,并且还解决了类型之间的命名冲突。命名空间是一种逻辑组合,而不是物理组合。其声明的基本格式:
• namespace qualified-identifier namespace-body ;
• 在上述声明中,命名空间使用的关键字“namespace”,其后跟名字空间名和名字空间主体,还必须跟分号。
5.5.2 导入命名空间
• 有了命令空间的基本知识,那么在不一样命令空间中的类是如何调用的了,请看代码,【本示例参考:\示例代码\Chap05\Testnamespace2】。
• 当编译上述代码时,会出现错误。缘由在于:在Testnamespace类中,直接调用了Test类。编译器认为两个类在同一个命令空间中,即类Test的完整名被认为是Chap05.Test,因此找不到类Test。只要把“new Test().WriteInfo();”这一句改成“new Chap05.SonPath.Test().WriteInfo();”就能够经过编译了。
5.5.3 使用指示符
• 【本节示例参考:\示例代码\Chap05】
• 使用指示符的目的是为了方便使用其余的命名空间中定义的命名空间和类型。其有两种类型:别名使用指示符和命名空间使用指示符。命名空间使用指示符在上一小节已经介绍。
• 别名使用指示符定义了一个别名,之后可使用这个别名来代替一个类型。这在两个库的名字可能发生冲突的状况下很是有用。别名还能够避免使用冗长的命名空间。其基本格式以下:
• using MS = MyProgram. MyCSharp. Chap05;
5.6 小结
• 本章接着上一节的内容继续介绍面向对象,主要内容是面向对象的继承性、多态性、封装性,同时还有在编程中常常遇到的类型转换、异常处理、命令空间。
• 面向对象的三大特征虽然解释起来很简单,可是要融会贯通却很难,读者千万不要被它们的假象所迷惑。后面的三个概念虽然看起来很陌生,可是学起来很是简单。所以,读者要把重心放在前三小节中。