本学习主要参考Andrew Troelsen的C#与.NET4高级程序设计,这小节主要述说以下几个东西:
1、如何使用继承来构建一组相关类。
2、如何使用虚成员和抽象成员在类层次结构中创建多态
接口。
3、.net基础类库中的超级父类System.Object。
继承的基本机制
继承是OOP的一个方面,可以促进代码重用,更具体地说,代码重用归为两类,经典继承(‘is-a’关系)和包含/委托模型(‘
has-a’关系)。
is-a关系:类之间的is-a关系也就是在两个或两个以上类类型之间创建了依赖关系,经典继承的基本思想是新类可以利用既有类的功能。
定义一个基类A如下:
class A
{
private int num;
public int Num
{
get{return num;}
set{num = value;}
}
public A()
{
Console.WriteLine(Num);
}
public A(int aaa)
{Console.WriteLine("this is A");}
}
1、指定既有类的父类。
如果我们定义一个类B让其继承A则定义如下:
class B:A
{}
如此,我们B类便得到了A类里面的公有类成员Num和公有方法
构造函数A()。(继承类不能访问父类的私有成员,继承保护了封装)这样就实现了代码重用。
2、多个基类。
说道基类,要记住很重要的一点:C#要求一个类只能有一个直接基类。我们不能创建直接派生自两个或两个以上的基类的类类型(多重继承)。不过,.net平台允许某一个类实现多个独立的接口。这样,C#类型可以实现很多行为,同时又避免了多重继承引起的复杂性。还可以通过构建灵活的接口层次来建模复杂的行为。
3、sealed关键字。
C#提供了另外一个关键字sealed来防止发生继承。如果我们将类标记为sealed,编译器将不允许我们从这个类型派生。例如,我们将B改成下面形式:
sealed class B:A
{}
如果C类想要继承B类的话,将会产生编译
错误。
class C:B//错误,不能扩展sealed标记的类。(string类是个密封的类,不能扩展。)
{}
说明:C#结构总是隐式密封的,因此,我们永远不可以从结构继承结构,从类继承结构或者从结构继承类。结构只能用于建模独立的、用户
自定义的数据类型。如果希望使用is-a关系,必须使用类。
OOP的第二个支柱:继承
1、使用base关键字来控制基类的创建。
C#下,一般基本的默认构造函数会在派生类构造函数执行之前被自动调用。为了帮助优化派生类的创建,最好实现子类构造函数,来显示调用合适的自定义基类构造函数,而不是默认构造函数。
class B:A
{public B(int a ):base(int aaa)}
说明一点,当我们为类定义增加了自定义的构造函数,默认构造函数就自动移除了。因此必要情况下,我们需要为子类重新定义默认构造函数。
2、家族的秘密:protected关键字。
我们已经知道,公共项可以在任何地方直接访问,而私有项除了定义它的类之外,不能从其他对象进行访问。C#提供了另外一个定义成员可访问性的关键字:protected。
当基类定义了受
保护数据或受保护成员时,它就创建了一组可以被任何后代访问的项。
如A类增加如下定义,B类就可以访问这些信息。
class A
{protected int rC;}
这样的好处,派生类不再需要使用公共方法或属性来间接访问数据了。
坏处,如果派生类有权直接访问其父类内部数据,有可能会偶然绕过公共属性内设置的既有业务规则。
最后要注意,对对象用户来说,受保护数据可以认为是私有的。因此,如果B b = new B();b.rc=10;就会产生错误。
3、增加密封类。
密封类是不能被其他类扩展的,这项技术通常用于设计工具类。如果我们希望构建新类来使用密封类的功能,唯一的办法就是使用包含/委托模型(也叫‘has-a’关系)。
包含/委托编程
我们创建一个类C,和A类的逻辑表示为A类包含C类。如果我们更新A类则就如下:
class A
{
protected C c= new C();
}
然而,如果我们要公开被包含对象的功能给外部世界,就需要委托。简单地说,委托就是增加公共成员到包含类,以便使用被包含对象的功能。
例如C类如下:
class C
{ public void PrintC(){Console.WriteLine("this is C");}}
A类如果想要使用C类的PrintC函数,需要增加如下定义
class A
{
public void GetPrintC()
{return c.PrintC();}
}
嵌套类型定义:这就是‘has-a’关系的另一种说法。在C#中我们可以直接在类或结构作用域定义类型(
枚举、类、接口、结构或委托),这样做,被嵌套类型会被认为是嵌套类的成员,嵌套类型可以操作其他成员一样来操作嵌套类型(可以访问其中的私有成员):
public Class D
{
public class C{}//公共嵌套类型可以被任何人使用
private class C{}//私有嵌套类型只可以被包含类的成员使用。
}
OOP的第三个支柱:C#的多态支持
1、virtual 和override关键字。
多态为子类提供了一种方式,使其可以定义由基类定义的方法,这种过程叫做方法重写。如果基类希望定义可以由子类重写的方法,就必须使用virtual关键字标志方法:
partial class A
{
public virtual void vA(int aaaaa){//操作逻辑}
}
如果子类希望改变虚方法的实现细节,就必须使用override关键字。
class B:A
{public override void vA(int aaaaa){//操作逻辑}}
注意,重写方法完全可以通过base关键字来自由的使用默认行为,这样我们就不需要完全重写实现vA方法的逻辑。
public override void vA(int aaaa){base.vA(aaaaa);//其余的处理逻辑}
2、密封虚成员。
sealed关键字,它可以用于类类型来防止其他类型通过继承扩展其行为。如果我们不希望密封整个类,而是只希望防止派生类来重写某个虚方法,我们就可以在方法前添加sealed关键字:
public override sealed void vA(int aaaaa){//处理逻辑}
3、抽象类。
由于许多基类都是比较模糊的实体,好的设计师会防止在代码中直接创建基类的对象(实例)。在C#中我们可以使用abstract关键字来强制这种编程方式,因此创建一个抽象基类:
abstract partial class A
{...}
此时,A a= new A();就会产生错误。
尽管我们不能直接创建抽象类,但在创建其派生类时,仍然会在
内存中对其进行组装。因此对抽象类来说,定义若干在分配派生类时间接调用的构造函数是很正常的。
4、构建多态接口。
如果类被定义为抽象基类,它就可以定义许多抽象成员。当你希望定义没有提供默认实现而必须在每个派生类中实现的成员时,可以使用抽象成员。这样做就强制了每一个后代具有多态接口(不同于interface定义的接口)。
简而言之,抽象基类的多态接口只是指一组虚的或者抽象的方法。
抽象方法和虚方法的区别:子类重写虚方法不是必须的,但是每个子类强制被要求重写基类的抽象方法。
说明:抽象方法只可以定义在抽象类中,如果不是这样,会出现编译错误。
标记abstract的方法是纯粹的xieyi9。它之定义了名字、返回值和参数集合(没有实现)。
5、成员投影。
C#提供了逻辑上和方法重写相对的功能,奇偶做投影。正式的说,如果派生类定义的成员和定义在基类中的成员一致,派生类就投影了父类的
版本。编译会产生警告。解决问题的办法如下:
1.我们可以只使用override关键字更新父类版本的成员。
2.我们可以为派生类型的相同成员添加new关键字。这样就显式声明派生类型的实现故意设计为隐藏父类的版本。(相当于和父类没有关系的成员)
我们还可以把new关键字应用到任何从基类继承的成员类型中(字段、常量、
静态方法、属性等)。相反,我们需要知道,我们也仍然可以使用显式强制转换来触发阴影成员在基类中的实现。
基类/派生类转换规则
在.net平台下,系统中的最高基类是System.Object。因此,所有东西都是(‘is-a’)object,并且可以照此进行处理。
object obj=new A();
类型之间强制转换的第一条准则:如果两个类通过is-a关系关联,在基类引用中保存派生类型总是安全的。(隐式转换)
类型之间强制转换的第二条准则:使用C#强制转换
caozuofu.html" target="_blank">操作符进行显式的向下转换。(强制转换)
1、C#的as关键字。
as关键字可以在运行时快速检测某个类型是否和另外一个兼容。如果我们使用as关键字就可以通过检查null返回值来检测兼容性。
2、C#的is关键字。
C#语言提供了is关键字来检测两个项是否兼容。然而,和as关键字不同的是,如果类型不兼容,is关键字就返回false而不是null引用。
注意,我们不需要使用try/catch结构来包装我们的强制转换操作,因为我们知道有了这样的条件检测,代码在if区域中的强制转换一定是安全的。
超级父类:System.Object
在.net世界中,每一个类型最终都会从一个叫做System.Object(可以用C#关键字object表示)的基类派生。Object类定义了一组框架中所有类型公共的成员。如果我们没有显式定义类的父类,编译器会自动从Object派生我们的类型。尽管其内置的行为能满足很多需要,但对于我们自定义类型来说,重写一些继承方法很常见:
重写ToString()方法。
public override string ToString()
{}
同重写ToString方法类似,我们还可以重写Equals方法、重写GetHashCode方法等等。
小结
本小节主要
研究了继承和多态的一些作用和细节。除此,我们还研究了如何在基类类型和派生类类型之间的显示转换。最后我们了解了一下超级父类System.Object的一些细节,结束了面向对象三大支柱继承、封装、多态的研究。