C#面向对象--继承

  一、通过继承(Inheritance)可以在创建新类时复用、扩展和重写已在其它类中声明的可访问的实例成员(除构造函数和析构函数外),类完全支持继承,可以继承自类也可以实现接口,结构不支持继承,只可以实现接口;继承是通过派生(Derivation)实现的,被继承的类称为基类(Base Class),继承的类称为派生类(Derived Class);通过在派生类的名称后面添加冒号和基类名称来指定继承的基类:

class MyBaseClass
{
    public void MyBaseFunc() {}
}
class MyClass : MyBaseClass //MyBaseClass是MyClass的基类,MyClass是MyBaseClass的派生类
{
    //do…
}

  使用时:

new MyClass().MyBaseFunc(); //MyClass继承了MyBaseClass的实例成员

  1.从概念上来说,派生类是基类的专门化,派生类会隐式获得基类中所有可访问的实例成员(除构造函数和析构函数外),派生类可以复用基类中的代码,而无需重新实现;派生类可以添加更多的成员,以扩展基类的功能;当基类中包含虚方法时,派生类可以重写该方法以实现不同的功能;

  2.派生类只能有一个直接基类,但是,继承是可传递的,例如ClassC派生自ClassB,ClassB派生自ClassA,那么ClassC会继承在ClassB和ClassA中声明的可访问的实例成员;

  3.派生类的可访问性不允许高于基类的可访问性,即公共类可以派生出内部类和公共类,而内部类只能派生出内部类;不能继承自密封类;

  4.所有没有显式继承基类的类都直接继承自基类System.Object;

  5.类可以声明为密封的来防止被其它类继承;

  6.在派生类中可以直接访问基类中的非私有静态成员,也可以直接或通过关键字base在任意位置访问直接基类(即派生类所直接继承的基类)中的非私有实例成员:

public void MyFunc()
{
    base.MyBaseFunc(); //通过base调用直接基类中的实例方法,此处base也可以省略
}

  7.派生类的实例可以通过隐式转换为基类类型的变量:

MyBaseClass myObj = new MyClass();

  ※此时myObj依然是MyClass类型的实例,使用myObj.GetType()方法获得的类型对象也是MyClass类型的:

myObj.GetType().Name //MyClass

  ※对于该对象,还可以通过显式转换或as运算符转换为派生类的对象然后赋值给派生类类型的变量,其原实例的任何数据都不会修改或丢失,详见:

MyClass myClass = (MyClass)myObj; //推荐使用as运算符:MyClass myClass = myObj as MyClass;

  ※这个操作需要变量myObj也是由MyClass或其派生类型的对象转换来的,不能直接将基类对象转换为派生类变量,会抛出异常InvalidCastException,如果myObj是由其它派生类的对象转换来的,而且该派生类和MyClass无法直接进行转换,则也会抛出这个异常;正确的做法是先使用is判断是否可以转换,如果可以再使用强制转换或as进行转换,也可以不进行判断直接使用as进行转换,转换失败时会返回null;

  8.派生类中如果需要有与基类相同名称的成员,需要使用关键字new来隐藏基类中的成员,如果不显式使用new,编译器会抛出警告并默认使用new来处理:

public new void MyBaseFunc() {}

  ※此时将派生类的对象隐式转换为基类的变量再访问该名称的方法时调用的依然是基类中的方法;

  ※此时在派生类中依然可以使用关键字base访问基类中声明的该同名成员;

  

  二、如果基类中包含虚成员(Virtual Member,只包括方法、属性、事件和索引器),那么在派生类中可以通过关键字override重新实现此成员,重新实现的成员被称为重写成员(Overridden Member);重写成员被看作是在派生类中声明的对基类虚成员的新实现,将代替基类中的虚成员,即通过将派生类对象隐式转换为基类变量时不能再访问基类的此虚成员,而是访问派生类中重写后的成员:

public class BaseClass
{
    public virtual void MyFunc()
    {
        Console.WriteLine("Base MyFunc");
    }
}
public class MyClass : BaseClass
{
    public override void MyFunc()
    {
        Console.WriteLine("Overridden MyFunc");
    }
}

  使用时:

BaseClass myObj = new MyClass();
myObj.MyFunc(); //Overridden MyFunc

  ※通过反射可以得知,使用override重写方法时,派生类的类型对象中只包含一个该名称的方法,即覆盖了基类中的虚方法,因此对于派生类对象,无论将其作为派生类变量访问还是作为基类变量访问,都只能访问到派生类中的重写方法;

  ※重写成员依然具有虚效果,在后续派生类中同样可以重写该成员;

  ※如果派生类中除了重写方法,还声明了该方法的重载方法,则在调用时会优先匹配派生类中直接声明的重载方法,再匹配派生类中对基类重新实现的重写方法,例如:

public class MyClass : BaseClass
{
    public override void MyFunc(int num)
    {
        Console.WriteLine("Overridden MyFunc");
    }
}
public class MyClass : BaseClass
{
    public override void MyFunc(int num)
    {
        Console.WriteLine("Overridden MyFunc");
    }
    public void MyFunc(double num)
    {
        Console.WriteLine("Overloaded MyFunc");
    }
}

  使用时:

BaseClass myObj = new MyClass();
myObj.MyFunc(1); //Overloaded MyFunc

  ※由于变量1可以隐式转换为double类型,因此编译器将调用MyFunc(double num),而不是MyFunc(int num),有两种方法可以避免此情况:1.避免新声明的方法与虚方法命名相同;2.可以将派生类的实例显式转换为基类来使编译器搜索基类的方法列表,从而调用基类中的该方法,由于该方法已被派生类重写,所以最终调用的是派生类中的重写方法

  ※如果基类中有虚方法A和B,其中方法B中调用了方法A,派生类中只重写了方法A,那么在调用运行时类型为派生类的变量的方法B时,其中在调用方法A时调用的是派生类中重写过的方法A;

  1.如果不想让基类中的虚成员被代替,则应该在派生类中通过关键字new隐藏此虚成员并提供新的实现,此成员被称为替换成员(Replaced Member);替换成员只是隐藏而不会代替基类中的虚成员,此时通过将派生类对象隐式转换为基类变量时访问到的依然是基类中的虚成员;

  ※通过反射可以得知,使用new隐藏基类虚方法并提供新的实现时,派生类的类型对象中包含两个该名称的方法,其中一个是派生类中新声明的方法,另一个是基类中被隐藏的虚方法,此时访问派生类对象,编译器会根据变量的运行时类型找到对应的方法,即运行时类型为派生类时访问的是派生类中新实现的方法,运行时类型为基类时访问的是基类中的虚方法;

  ※替换成员隐藏了基类中的虚成员,在后续派生类中也不可以再重写该成员;

  2.即使在派生类中重写或隐藏了基类的虚成员,也可以在派生类的任意位置通过关键字base访问直接基类中该虚成员的实现;推荐在派生类中的重写成员时通过关键字base调用直接基类中该成员的实现,使派生类能够集中精力实现属于派生类的特殊行为:

public override void MyFunc()
{
    base.MyFunc(); //通过base关键字访问直接基类中MyFunc()的实现
    //do…
}

  ※通过这种方式,还可以获取基类中声明虚自动属性时初始化的值;

  3.在继承关系中,不管派生类中是否重写了虚成员,只要没有替换虚成员,虚成员的虚效果都会一直传递下去,除非在派生类中使用组合关键字sealed override重写虚成员并将其指定为密封的,此时将取消该成员的虚效果,该派生类的后续派生类中不可以再重写该成员,从而阻止该成员的进一步派生;与使用替换成员阻止派生不同的是,使用这种方式声明的成员依然是重写方法,会代替基类中的虚成员,转换为基类变量使用时调用的依然是当前类中的重写成员:

public sealed override void MyFunc()
{
    //do…
}

  

  三、如果派生类继承自一个抽象类,就必须在派生类中通过关键字override实现抽象基类中所有的抽象成员,除非派生类也是抽象的:

public abstract class MyAbstractClass
{
    public abstract void MyFunc();
}
public class MyClass : MyAbstractClass
{
    public override void MyFunc()
    {
        //
    }
}

  ※如果派生类也是抽象类,则会默认继承基类中的抽象成员而无需实现;

  ※派生类的派生类中可以使用override重写该成员,也可以使用new替换该成员;

  1.虚成员、抽象成员和重写成员、替换成员是多态的基础;


如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的认可是我写作的最大动力!

作者:Minotauros
出处:https://www.cnblogs.com/minotauros/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文地址:https://www.cnblogs.com/minotauros/p/11773853.html