继承是面向对象的一大特征。要深刻学习继承,需要学会使用调试的技巧来学习它,因为它比较抽象。
- 继承
- 继承是指一个具体的类型直接使用另一类型的某些数据成员或函数成员,继承的类是基类(父类),被继承的类是派生类(子类)。所有的类继承Object类,并且拥有Object所有公共方法.
- 继承类型
- 实现继承和接口继承
- 实现继承,表示一个类型派生自一个基类,并且拥有基类所有的公共成员。如果派生类需要重写编写基类的公共方法,则需要使用重写基类方法。
- 实现继承,利于类型的代码重用和扩展。
- 接口继承,表示一个类型继承了函数的签名。没有继承任何实现。需要指定类具有某些可用性时可使用接口继承。
- 多重继承
- 多重继承,一个类派生自多个类型。
- C#不支持多重实现继承,但是允许类型派生自多个接口(多重接口继承)
- 结构和类
- 结构不支持实现继承,但支持多接口继承,因为结构本身继承了ValueType类型。
- 实现继承和接口继承
- 实现继承
- 实现继承,一个类型派生自一个基类,但是可以派生自多个接口,如果类型既派生基类又派生接口,需要先知道基类,再指定接口。
- 我们定义的类型,编译器实际上默认指定了类型派生自Object类型。
-
和
- 虚方法
- 把基类的函数或者属性可以设为virtual,就可以在派生类中进行重写。
- 在子类中重写虚属性或虚方法,需要使用override关键字显式声明。这样避免了类似C++的潜在错误。
- 成员字段和静态函数不能声明为virtual,因为virtual只对实例成员有效。
- 隐藏方法
- 基类定义的方法与子类定义的方法名相同,且并没有在基类中声明Virtual关键字,所以不是重写方法。而子类隐藏了基类的方法。此时可以使用new关键字来标识子类方法
- 调用函数基类版本
- 在子类内部需要调用基类的成员属性,使用base.属性名可以获取和设置基类属性值。同样使用base.方法名可以调用基类的方法。
- 抽象类和抽象函数
- 抽象类使用abstract关键字声明。抽象类不能实例化,抽象类中含有抽象方法,抽象方法不需要在抽象类中实现。普通类中不能有抽象方法,有抽象方法的必须是抽象类。抽象类的抽象方法使用abstract声明,抽象类中也允许包含一般方法。
- 继承抽象类的派生类,需要实现抽象类所有的抽象方法,并且使用override进行重写实现的方法。
- 密封类和密封方法
- C#允许把类和方法声明为sealed,对于类,这表示不能继承该类,对于方法,这表示不能重写该方法。
- 对于自己编写的类或方法进行操作时,可能会对类造成代码混乱或问题,或因为商业原因防止扩展该类,所以进行sealed标记.
- 派生类的构造函数
- 类一般有默认的构造函数,如果自定义了构造函数之后,编译器将不再默认构造函数。
- 子类与基类都会有构造函数,在实例化时,会先执行基类的构造函数,再执行子类的构造函数。
- 同样子类本身也会有多个构造函数。构造函数又可以调用自身的构造函数。那么执行顺序是什么呢?
- 先执行基类构造函数,再执行调用的构造函数,最后调用构造函数
- 在层次结构中添加无参数的构造函数
- 在子类和基类定义了构造函数,则编译器不再使用默认构造器,并且先调用基类的构造函数再调用子类的构造函数,但是子类也可以调用基类的构造函数,还可以调用本身类中的构造函数。调用基类的构造函数使用base,调用本身类中的构造函数使用this关键字。
- 在层次结构中添加带参数的构造函数
- 如果创建了一个带参数的构造函数实例对象,则势必要在创建实例是传入参数进行初始化。
- 如果类具有一个或多个私有构造函数而没有公共构造函数,则其他类(除嵌套类外)无法创建该类的实例。
- 修饰符
- 访问修饰符(可见修饰符)
- 访问修饰符可以修饰类型、类型成员。修饰符标准介绍如下:
- 如果类嵌套,也就是说类中含类,那么类中类可以访问类的所有成员,包含私有的信息。
- 其他修饰符
- 访问修饰符(可见修饰符)
- 接口
- 定义和实现接口
- 接口不能被实例化,使用interface声明,接口中的所有成员是公有的,因此无需使用访问修饰符修饰。接口中不能定义属性、字段,只能定义方法,方法不能含方法体。继承接口的类,需要实现接口的所有方法。
- 派生接口
- 接口是允许继承接口的,接口的实现必须有具体的类型实现。继承接口的类,必须实现继承接口的所有方法以及继承接口的所有方法。
interface IBook
{
string GetBookName();
}
interface IEnglish : IBook
{
string GetPersonEngName();
}
class MerEngser : IEnglish
{ public string GetPersonEngName()
{
throw new NotImplementedException();
} public string GetBookName()
{
throw new NotImplementedException();
}
}
- 抽象类和接口的区别:
- 相同点:
- 都可以被继承
- 都不能被实例化
- 都可以包含方法声明
- 派生类必须实现未实现的方法
- 不同点:
- 抽象基类可以定义字段、属性、方法实现。接口只能定义属性、索引器、事件、和方法声明,不能包含字段。
- 抽象类是一个不完整的类,需要进一步细化,而接口是一个行为规范。微软的自定义接口总是后带able字段,证明其是表述一类“我能做。。。”
- 接口可以被多重实现,抽象类只能被单一继承
- 抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现某一功能的类中
- 抽象类是从一系列相关对象中抽象出来的概念, 因此反映的是事物的内部共性;接口是为了满足外部调用而定义的一个功能约定, 因此反映的是事物的外部特性
- 接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法
- 接口可以用于支持回调,而继承并不具备这个特点
- 抽象类实现的具体方法默认为虚的,但实现接口的类中的接口方法却默认为非虚的,当然您也可以声明为虚的
- 如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法
- 不同点:
- 重写和重载的区别:
- Overload:重载就是在同一个类中,方法名相同,参数列表不同。参数列表不同包括:参数的个数不同,参数类型不同等。
override:说的是两个类继承,子类重写父类的方法,在调用的时候,子类的方法会覆盖父类的方法,也就是会调用子类的方法。