继承的类型
在面向对象的编程中,有两种截然不用的继承类型:
- 实现继承:表示一个类型派生于一个基类型,拥有该基类型的所有成员字段和函数。派生类型的每个函数采用基类型的实现代码,除非派生类型的定义中指定重写该函数的实现代码。在需要给现有的类型添加功能,或许多相关的类型共享一组重要的公共功能时,这种类型的继承是非常有效的。
- 接口继承:表示一个类型只继承了函数的签名,没有继承任何实现代码。在需要指定该类型具有某些可用的特性时,最好使用这种类型的继承。接口继承通常被看作提供了一种契约:让类型派生于接口,来保证为客户提供某个功能。
C#不支持多重继承。而C#又允许类型派生于多个接口。因为System.Object是一个公共基类,所以每个C#类(除了Object类外)都有一个基类,还可以有任意多个基接口。
结构并不支持实现继承,但支持接口继承。
- 结构总是派生于System.ValueType,它们还可以派生于任意多个接口。
- 类总是派生于用户选择的另一个类,它们还可以派生于任意多个接口。
实现继承
如果要声明一个类派生于另一个类,使用下面的语法:
... {
.........
}
如果类(或结构)也派生于接口,则用逗号分隔开基类和接口:
... {
.....
}
... {
........
}
如果在类定义中没有指定基类,C#编译器就假定System.Object是基类。
... {
......
}
C#支持object关键字,它用做System.Object类的假名。
... {
.......
}
虚方法
把一个基类的函数声明为virtual,该函数就可以在派生类中重写了:
... {
public virtual string MethodOne()
...{
return "it`s baseclass";
}
}
也可以把属性声明为virtual。
public virtual string ForeName
... {
get
...{
return foreName;
}
set
...{
froeName = vlaue;
}
}
C#中虚函数的概念与标准OOP概念相同:可以在派生类中重写虚函数。C#要求在派生类的函数重写另一个函数时,要使用override关键字显式声明:
... {
public override string MethodOne()
...{
return "this is new method" ;
}
}
成员字段和静态函数都不能声明为virtual,因为这个概念只对类中的实例函数成员有意义。
隐藏方法
如果签名相同的方法在基类和派生类中都进行了声明,但该方法没有声明为virtual和override,派生类方法就会隐藏基类的方法。
在大多数情况下是要重写方法而不是隐藏方法,因为隐藏方法会存在为给定类的实例调用错误方法的危险。在C#中,应使用new关键字声明我们要隐藏的一个方法:
... {
public new string MethodOne()
...{
return "......";
}
}
调用函数的基类版本
C#有一种特殊的语法用于从派生类中调用方法的基类版本:base.<MethodName>()。
... {
public override string MethodOne()
...{
return "....."+base.MethodOne();
}
}
可以使用这个语法调用基类中的任何方法,不必在同一个方法的重载中调用它。
抽象类和抽象函数
C#允许把类和函数声明为abstract,抽象类不能实例化,而抽象函数没有执行代码,必须在非抽象的派生类中重写。显然抽象函数也是虚拟的。如果类包含抽象函数,该类将也是抽象的,也必须声明为抽象的:
... {
private bool damaged = false;
public abstract decimal Cost();
}
密封类和密封方法
C#允许把类和方法声明为sealed。对于类来说,这表示不能继承该类;对于方法来说,这表示不能重写该方法。
... {
.......
}
class MyClass
... {
public sealed void MehodOne()
...{
..........
}
}
在把类或方法标记为sealed时,最可能的情形是:如果要对库、类或自己编写的其他类进行操作,则重写某些方法会导致错误。也可以因商业原因把类或方法标记为sealed。
在方法上使用sealed没有意义,除非该方法本身是某个基类上另一个方法的重写形式。
派生类的构造函数
... {
private string name;
............
}
class Nevermore60Customer : GenericCustomer
... {
private uint hightCostMinutesUsed;
...........
}
假定默认的构造函数在整个层次结构中使用:编译器首先找到它试图实例化的类的构造函数,默认Nevermore60Customer构造函数首先要做的是为其直接基类GenericCustomer运行默认构造函数,然后GenericCustomer构造函数为其直接基类System.Object运行默认构造函数,System.Object没有任何基类,所以它的构造函数就执行,并把控制权返回给GenericCustomer构造函数。现在执行GenericCustomer构造函数,把name初始化为null,再把控制劝返回给Nevermore60Customer构造函数,接着执行这个构造函数。把hightCostMinutesUsed初始化为0,并退出
构造函数的调用顺序是先调用System.Object,再按照层次结构由上向下进行,直到到达编译器要实例化的类为止。每个构造函数都要初始化它自己的类中的字段。
基类的构造函数总是最先调用,也就是说,派生类的构造函数可以在执行过程中调用它可以访问的基类方法、属性和其他成员。
在层次结构中添加无参数的构造函数
在层次结构中用一个无参数的构造函数来替换默认的构造函数: 接口
... {
private string name;
public GenericCustomer()
...{
name = "<no name>";
}
}
编译器会使用定制的构造函数,而不是生成默认的构造函数。如果编译器没有在起始花括号的前面找到对另一个构造函数的任何引用,它就会假定要调用基类构造函数。
在层次结构中添加带参数的构造函数
首先是带一个参数的构造函数:
... {
private string name;
public GenericCustomer(string name)
...{
this.name = name;
}
}
需要为派生类提供一个构造函数,因为编译器为派生类生成的默认构造函数会试图调用无参数的基类构造函数,但GenericCustomer没有这样的构造函数。
... {
private uint hightCostMinutesUsed;
public Nevermore60Customer(string name) : base(name)
...{
......
}
}
Nevermore60Customer构造函数不身不能初始化name字段,因为不能访问基类中的私有字段,但可以把name传给基类,使用基类构造函数处理。
下面讨论如果要处理不同的重载构造函数:
... {
private uint hightCostMinutesUsed;
private string referrerName;
public Nevermore60Customer(string name , string referrerName) : base(name)
...{
this.referrerName = referrerName;
}
public Nevermore60Customer(string name) : this(name , "<None>")
...{
}
}
执行下面的代码
编译器认为她需要带一个字符串参数的构造函数
在实例化cutomer时,就会调用这个构造函数,之后把控制权传给对应的构造函数,该构造函数带2个参数。在这个构造函数中,把控制权依次传给GenericCustomer构造函数,该构造函数带有一个参数。然后这个构造函数把控制权传给System.Object默认构造函数,然后执行这些构造函数。
修饰符
修饰符可以指定方法的可见性,还可以指定一项的本质。
可见性修饰符:
修饰符 | 应用于 | 说明 |
public | 所有的类型或成员 | 任何代码均可以访问该方法 |
protected | 类型和内嵌类型的所有成员 | 只有派生类型能访问该方法 |
internal | 类型和内嵌类型的所有成员 | 只能在包含它的程序集中访问该方法 |
private | 所有的类型或成员 | 只能在它所属的类型中访问该方法 |
protected internal | 类型和内嵌类型的所有成员 | 只能在包含它的程序集或派生类型的代码中访问该方法 |
类型定义可以是内部或公共的,这取决于是否希望在包含类型的程序集外部访问它。
不能把类型定义为protected、private和protected internal,这些修饰符只能用于成员,但是可以用这些定义嵌套的类型。如果有嵌套的类型,内部的类型总是可以访问外部类型的所有成员。
其他修饰符:
修饰符 | 应用于 | 说明 |
new | 函数成员 | 成员用相同的签名隐藏继承的成员 |
static | 所有的成员 | 成员不在类的具体实例上执行 |
virtual | 仅类和函数成员 | 成员可以由派生类重写 |
abstract | 仅函数成员 | 虚拟成员定义了成员的签名,但没有提供实现代码 |
override | 仅函数成员 | 成员重写了继承的虚拟或抽象成员 |
sealed | 类 | 成员重写了继承的虚拟成员,但继承该类的任何类都不能重写该成员。该修饰符必须与override一起使用 |
extern | 仅静态方法 | 成员在外部用另一语言实现 |