在面向对象编程中,有两种截然不同的继承类型,实现继承和接口继承。C# 不支持多重继承,C#类可以派生自另一个类和任意多的接口。

  • 实现继承:表示一个类型派生自一个基类型,它拥有该基类型的所有成员字段和函数,在需要给现有类型添加功能或者许多相关类型共享一组重要的公共功能时这种类型继承非常有用
  • 接口继承:表示一个类型只继承了函数的签名,没有继承任何的实现代码

实现继承

  1. /// <summary>
  2. /// 基类
  3. /// </summary>
  4. class Person
  5. {
  6. /// <summary>
  7. /// 使用virtual关键字定义的方法允许在派生类中使用override重写
  8. /// </summary>
  9. public virtual void SayHello()
  10. {
  11. Console.WriteLine("基类的SayHello");
  12. }
  13. }
  14. /// <summary>
  15. /// 派生自Person
  16. /// </summary>
  17. class ChinaPerson : Person
  18. {
  19. /// <summary>
  20. /// 使用override关键字重写基类的SayHello方法
  21. /// </summary>
  22. public override void SayHello()
  23. {
  24. Console.WriteLine("你好");
  25. }
  26. }
  27. /// <summary>
  28. /// 派生自Person
  29. /// </summary>
  30. class ThailandPerson : Person
  31. {
  32. public override void SayHello()
  33. {
  34. Console.WriteLine("萨瓦迪卡");
  35. }
  36. }

把一个基类函数声明为 virtual,就可以在任何派生类中重写该函数,virtual 也适用于属性。

注意;成员字段和静态函数都不能声明为 virtual ,因为这个概念只对类中的实例成员有意义

隐藏方法

如果签名相同的方法在基类和派生类中都进行了声明,但该方法没有声明为 virtualoverride,派生类会隐藏基类方法。

  1. class Person
  2. {
  3. public void SayHello()
  4. {
  5. Console.WriteLine("基类的SayHello");
  6. }
  7. }
  8. class ChinaPerson : Person
  9. {
  10. //提示:隐藏继承的成员Person.SayHello,如果有意的,请使用关键字new
  11. public void SayHello()
  12. {
  13. Console.WriteLine("你好");
  14. }
  15. }

调用基类方法

C#中可以使用 base. 这种语法来调用方法的基类版本。

  1. class Person
  2. {
  3. public virtual void SayHello()
  4. {
  5. Console.WriteLine("基类的SayHello");
  6. }
  7. }
  8. class ChinaPerson : Person
  9. {
  10. public override void SayHello()
  11. {
  12. base.SayHello();
  13. Console.WriteLine("你好");
  14. }
  15. }

接口继承

表示一个类型只继承了函数的签名,没有继承任何实现代码。在需要指定该类型具有某些可用的特性时,最好使用这种类型的继承。

接口名称通常以字母 I 开头,以便知道这是一个接口。C#支持多接口继承和单一实现继承,接口继承中又分为隐式实现和显式实现。

  1. interface IPerson
  2. {
  3. void SayHello();
  4. }
  5. /// <summary>
  6. /// 隐式实现接口
  7. /// </summary>
  8. class ChinaPerson : IPerson
  9. {
  10. public void SayHello()
  11. {
  12. Console.WriteLine("你好");
  13. }
  14. }
  15. /// <summary>
  16. /// 显式实现接口
  17. /// </summary>
  18. class ThailandPerson : IPerson
  19. {
  20. void IPerson.SayHello()
  21. {
  22. Console.WriteLine("莎娃迪卡");
  23. }
  24. }

对于隐式实现的接口调用这两种方式都可以

  1. ChinaPerson chinaPerson = new ChinaPerson();
  2. IPerson person = new ChinaPerson();
  3. person.SayHello();
  4. chinaPerson.SayHello();

对于显式实现的接口调用只能使用接口调用

  1. IPerson thailandPerson = new ThailandPerson();
  2. thailandPerson.SayHello();

抽象类和抽象函数

  • C#中允许把类或函数声明为abstract ,抽象类不能被实例化。抽象函数也不能直接实现,必须在非抽象的派生类中重写
  • 如果类包含抽象函数,则该类也必须被声明为抽象的
  • 抽象方法只在派生类中真正实现,这表明抽象方法只存放函数原型不涉及主体代码
  • 派生自抽象类的类需要实现其基类的抽象方法,才能实例化对象
  • 使用 override 关键字可在派生类中实现抽象方法,经 override 声明重写的方法称为重写基类方法,其签名必须与 override 方法的签名相同

密封类和密封方法

C#允许把类和方法声明为 sealed ,对于类这表示不能继承;对于方法这表示不能重写该方法。

派生类的构造函数执行顺序

首先定义基类 A,为了方便查看,显式指明基类的无参构造函数

  1. class A
  2. {
  3. public int Age { get; set; }
  4. public string Name { get; set; }
  5. public A()
  6. {
  7. Console.WriteLine("A类无参构造函数");
  8. }
  9. }

然后定义B类继承自A

  1. class B : A
  2. {
  3. public B()
  4. {
  5. Console.WriteLine("B类无参构造函数");
  6. }
  7. }

实例化B

  1. static void Main(string[] args)
  2. {
  3. B b = new B();
  4. Console.ReadKey();
  5. }

输出:
A类无参构造函数
B类无参构造函数

实例化子类时,只可以new子类,执行顺序为:先执行父类构造函数=>再执行子类构造函数

如果父类存在多个构造函数会怎么样?

  1. class A
  2. {
  3. public int Age { get; set; }
  4. public string Name { get; set; }
  5. public A()
  6. {
  7. Console.WriteLine("A类无参构造函数");
  8. }
  9. public A(int age string name)
  10. {
  11. Console.WriteLine("A类带参构造函数");
  12. }
  13. }
  14. class B : A
  15. {
  16. public B()
  17. {
  18. Console.WriteLine("B类无参构造函数");
  19. }
  20. }

再次实例化B

  1. static void Main(string[] args)
  2. {
  3. B b = new B();
  4. Console.ReadKey();
  5. }

输出:
A类无参构造函数
B类无参构造函数

实例化子类时,会先执行父类的构造函数(默认为父类的无参构造函数),也可以在子类中使用base关键字指定调用父类的哪个构造函数

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. B b = new B(3);
  6. Console.ReadKey();
  7. }
  8. }
  9. class A
  10. {
  11. public int Age { get; set; }
  12. public A()
  13. {
  14. Console.WriteLine("A类无参构造函数");
  15. }
  16. public A(int age)
  17. {
  18. Console.WriteLine("A类带参构造函数");
  19. }
  20. }
  21. class B : A
  22. {
  23. public B() : base(3)
  24. {
  25. Console.WriteLine("B类无参构造函数调用父类带参构造函数");
  26. }
  27. }

输出:
A类带参构造函数
B类无参构造函数调用父类带参构造函数

总结:

  • 实例化父类时,可以使用 new 子类,执行构造函数顺序为:执行父类构造函数=>执行子类构造函数
  • 实例化子类时,只可以 new 子类,执行顺序同上
  • 父类实例化后,只能执行父类的方法,获得父类的属性等
  • 实例化子类后,可同时执行子类和父类的方法和属性,如同名方法,则执行子类的方法
  • 子类构造函数可以使用 base 关键字指定调用的父类构造函数

抽象类和接口的区别

相同点

  • 都可以被继承
  • 都不能被实例化
  • 都包含方法声明
  • 派生类必须实现未实现的方法

区别

  • 抽象基类可以定义字段/属性/方法实现.接口只能定义属性/索引器/事件/方法声明
  • 抽象类是一个不完整的类,需要通过集成进一步细化。而接口更像是一个行为规范表明能做什么
  • 接口是可以被多重实现的,可以有多个类实现接口,因为类的单一继承性,抽象类只能被单一继承
  • 抽象类实现继承需要使用 override 关键字,接口则不用
  • 如果抽象类实现接口,可以把接口方法映射到抽象类中作为抽象方法不必实现,而在抽象类的子类中实现接口方法
  • 抽象类表示的是这个对象是什么;接口表示的是这个对象能做什么;使用抽象类是为了代码的复用,使用接口是为了实现多态性

普通类和抽象类的区别

  • 都可以被继承
  • 抽象类不能实例化,普通类允许实例化
  • 抽象方法只包含方法声明而且必须包含在抽象类中
  • 子类继承抽象类必须实现抽象类中的抽象方法除非子类也是抽象类
  • 抽象类中可以包含抽象方法和实例方法