抽象类和抽象成员
- 使用abstract声明的类是抽象类
- 抽象类不可以被实例化,只有其具体的子类(不是抽象类)才可以实例化
- 抽象类可以定义抽象成员
- 抽象成员和virtual成员很像,但是不提供具体的实现。子类必须提供实现,除非子类也是抽象的
public abstract class AbstractClass
{
public decimal TestValue { get; }
public abstract decimal NetValue { get; }
}
public class SonClass : AbstractClass
{
public long SharesOwned;
public decimal CurrentPrice;
//必须重写抽象成员,使用Override关键字重写,和virtual类似
public override decimal NetValue => CurrentPrice * SharesOwned;
}
隐藏被继承的成员(隐藏父类成员)
- 父类和子类可以定义相同的成员:
public class A { public int IntCount = 1; }
public class B : A { public int IntCount = 2; }
- 上面Class B中的Counter字段就隐藏了A里面的Counter字段(通常是偶然发生的)。例如子类添加某个字段之后,父类也添加了相同的一个字段。
- 编译器会发生警告
对隐藏成员是如何解析的
- 编译时,对A的引用会绑定到A.IntCount
- 编译时,对B的引用会绑定到B.IntCount
public class A { public int IntCount = 1; }
public class B : A { public int IntCount = 2; }
public class Program
{
static void Main()
{
A a = new A();
System.Console.WriteLine(a.IntCount);//变量类型A 输出1
B b = new B();
System.Console.WriteLine(b.IntCount);//变量类型B 输出2
A x = new B();
System.Console.WriteLine(x.IntCount);//变量类型A 输出1
}
}
如果故意隐藏父类的成员,可以在子类的成员前面加上new修饰符
这里new修饰符仅仅会抑制编译器的警告而已(就是告诉编译器我是故意这样写的,你别警告我了)
SEALED(密封)
针对重写的成员,可以使用sealed关键字把它“密封”起来,防止它被其子类重写
public sealed override decimal NetValue => CurrentPrice * SharesOwned;
也可以sealed类本身,就隐式的sealed所有的virtual函数了
BASE关键字
base和this略像,base主要用于:
从子类访问父类里被重写的函数(virtual关键字修饰的)
调用父类的构造函数
下面这种写法可保证,访问一定是父类的属性,无论该属性是被重写还是被隐藏了
class classParent { public virtual int property { get; } }
class classChild : classParent
{
public int childProperty { get; }
public override int property => base.property + childProperty;
}
构造函数和继承
子类必须声明自己的构造函数
从子类可访问父类的构造函数,但不是自动继承的
子类必须重新定义它想要暴露的构造函数
(基本的意思就是子类的写自己的构造函数,可调用父类的构造函数)
一些具体的代码
class BaseClass
{
public int X;
public BaseClass() { System.Console.WriteLine("Hello"); }
public BaseClass(int x) { this.X = x; }
public BaseClass(string x) { System.Console.WriteLine(x); }
}
class SubClass : BaseClass
{
//public SubClass(int x) : base(x){}
public SubClass(string x) : base(x) { }
static void Main()
{
SubClass sub = new SubClass("SubClass"); // print :SubClass
}
}
隐式调用无参的父类构造函数
如果子类的构造函数里没有使用base关键字,那么父类的无参构造函数会被隐式的调用
using System;
class BaseClass
{
public int X;
public BaseClass() { X = 1; }
}
class SubClass : BaseClass
{
public SubClass() { Console.WriteLine(X); } // print:1
static void Main()
{
SubClass sub = new SubClass();
}
}
如果父类没有无参构造函数,那么子类就必须在构造函数里使用base关键字来调用父类的其它构造函数这样才行
构造函数和字段初始化顺序
对象被实例化时,初始化动作按照如下顺序进行:
- 从子类到父类:
- 字段被初始化
- 父类构造函数的参数值被算出
- 从父类到子类
- 构造函数体被执行
重载和解析
例子
class BaseClass { }
class SubClass : BaseClass { }
class program
{
// 重载
static void Method(BaseClass baseclass) { }
static void Method(SubClass subclass) { }
}
- 从子类到父类:
重载方法被调用时,更具体的类型有用更高的优先级
class BaseClass { }
class SubClass : BaseClass { }
class program
{
// 重载
static void Method(BaseClass baseclass) { }
static void Method(SubClass subclass) { }
static void Main()
{
SubClass sub = new SubClass();
Method(sub); // 调用 Method(SubClass)
BaseClass bc = new SubClass();
Method(bc); // 调用 Method(BaseClass)
}
}