抽象类和抽象成员

  • 使用abstract声明的类是抽象类
  • 抽象类不可以被实例化,只有其具体的子类(不是抽象类)才可以实例化
  • 抽象类可以定义抽象成员
  • 抽象成员和virtual成员很像,但是不提供具体的实现。子类必须提供实现,除非子类也是抽象的
  1. public abstract class AbstractClass
  2. {
  3. public decimal TestValue { get; }
  4. public abstract decimal NetValue { get; }
  5. }
  6. public class SonClass : AbstractClass
  7. {
  8. public long SharesOwned;
  9. public decimal CurrentPrice;
  10. //必须重写抽象成员,使用Override关键字重写,和virtual类似
  11. public override decimal NetValue => CurrentPrice * SharesOwned;
  12. }

隐藏被继承的成员(隐藏父类成员)

  • 父类和子类可以定义相同的成员:
    1. public class A { public int IntCount = 1; }
    2. public class B : A { public int IntCount = 2; }
  • 上面Class B中的Counter字段就隐藏了A里面的Counter字段(通常是偶然发生的)。例如子类添加某个字段之后,父类也添加了相同的一个字段。
  • 编译器会发生警告
    • 未命名图片.png
  • 对隐藏成员是如何解析的

    • 编译时,对A的引用会绑定到A.IntCount
    • 编译时,对B的引用会绑定到B.IntCount
      1. public class A { public int IntCount = 1; }
      2. public class B : A { public int IntCount = 2; }
      3. public class Program
      4. {
      5. static void Main()
      6. {
      7. A a = new A();
      8. System.Console.WriteLine(a.IntCount);//变量类型A 输出1
      9. B b = new B();
      10. System.Console.WriteLine(b.IntCount);//变量类型B 输出2
      11. A x = new B();
      12. System.Console.WriteLine(x.IntCount);//变量类型A 输出1
      13. }
      14. }
  • 如果故意隐藏父类的成员,可以在子类的成员前面加上new修饰符

  • 这里new修饰符仅仅会抑制编译器的警告而已(就是告诉编译器我是故意这样写的,你别警告我了)

    未命名图片.png

    SEALED(密封)

    针对重写的成员,可以使用sealed关键字把它“密封”起来,防止它被其子类重写

    1. public sealed override decimal NetValue => CurrentPrice * SharesOwned;

    也可以sealed类本身,就隐式的sealed所有的virtual函数了

    BASE关键字

    base和this略像,base主要用于:
    从子类访问父类里被重写的函数(virtual关键字修饰的)
    调用父类的构造函数
    下面这种写法可保证,访问一定是父类的属性,无论该属性是被重写还是被隐藏了

  1. class classParent { public virtual int property { get; } }
  2. class classChild : classParent
  3. {
  4. public int childProperty { get; }
  5. public override int property => base.property + childProperty;
  6. }

构造函数和继承

子类必须声明自己的构造函数
从子类可访问父类的构造函数,但不是自动继承的
子类必须重新定义它想要暴露的构造函数
(基本的意思就是子类的写自己的构造函数,可调用父类的构造函数)
未命名图片.png
一些具体的代码

  1. class BaseClass
  2. {
  3. public int X;
  4. public BaseClass() { System.Console.WriteLine("Hello"); }
  5. public BaseClass(int x) { this.X = x; }
  6. public BaseClass(string x) { System.Console.WriteLine(x); }
  7. }
  8. class SubClass : BaseClass
  9. {
  10. //public SubClass(int x) : base(x){}
  11. public SubClass(string x) : base(x) { }
  12. static void Main()
  13. {
  14. SubClass sub = new SubClass("SubClass"); // print :SubClass
  15. }
  16. }

隐式调用无参的父类构造函数

如果子类的构造函数里没有使用base关键字,那么父类的无参构造函数会被隐式的调用

  1. using System;
  2. class BaseClass
  3. {
  4. public int X;
  5. public BaseClass() { X = 1; }
  6. }
  7. class SubClass : BaseClass
  8. {
  9. public SubClass() { Console.WriteLine(X); } // print:1
  10. static void Main()
  11. {
  12. SubClass sub = new SubClass();
  13. }
  14. }

如果父类没有无参构造函数,那么子类就必须在构造函数里使用base关键字来调用父类的其它构造函数这样才行

构造函数和字段初始化顺序

  • 对象被实例化时,初始化动作按照如下顺序进行:

    • 从子类到父类:
      • 字段被初始化
      • 父类构造函数的参数值被算出
    • 从父类到子类
      • 构造函数体被执行
      • 未命名图片.png


    重载和解析

    例子

    1. class BaseClass { }
    2. class SubClass : BaseClass { }
    3. class program
    4. {
    5. // 重载
    6. static void Method(BaseClass baseclass) { }
    7. static void Method(SubClass subclass) { }
    8. }

重载方法被调用时,更具体的类型有用更高的优先级

  1. class BaseClass { }
  2. class SubClass : BaseClass { }
  3. class program
  4. {
  5. // 重载
  6. static void Method(BaseClass baseclass) { }
  7. static void Method(SubClass subclass) { }
  8. static void Main()
  9. {
  10. SubClass sub = new SubClass();
  11. Method(sub); // 调用 Method(SubClass)
  12. BaseClass bc = new SubClass();
  13. Method(bc); // 调用 Method(BaseClass)
  14. }
  15. }