8.1 类继承
class Monster // 基类
{
public int hp, atk, def;
}
class Wizard : Monster // 派生类
{
public int mp;
}
通过继承基类可以定义一个派生类,派生类的成员包括自身声明中的成员以及基类的成员。
8.2 访问继承的成员
正常访问即可,略。
8.3 所有类都派生自 object
类
严格地说,除了object
,所有的类都是派生类。
没有基类规格说明的类都隐式地直接派生自类object
。
关于类继承,注意:
- 只能单继承:基类规格说明中只能有一个单独的类;
- 派生的层次没有限制。
8.4 屏蔽基类的成员
class Monster
{
public string name;
public int hp, atk, def;
}
class Wizard : Monster
{
new public string name;
public int mp;
}
虽然派生类不能删除它继承的任何成员,但可以用与基类成员名称相同的成员来屏蔽基类成员。要点如下:
- 如何屏蔽数据成员?声明一个新的相同类型的成员,并使用相同的名称;
- 如何屏蔽函数成员?声明新的带有相同签名的函数成员;
- 尽量使用
new
修饰符让编译器知道你在屏蔽,否则可能有 warning; - 静态成员也可以被屏蔽。
8.5 基类访问
class Monster
{
public string name;
public int hp, atk, def;
}
class Wizard : Monster
{
new public string name;
public int mp;
public void PrintBaseName()
{
Console.WriteLine($"Name: { base.name }"); // 基类访问
}
}
如果派生类需要访问被隐藏的继承成员,可以使用基类访问表达式:base.FieldName
如果程序中经常使用这个特性,可能需要重新进行类的设计。
8.6 使用基类的引用
Wizard derived = new Wizard();
Monster baseReference = (Monster)derived;
这个例子中,baseReference
就是一个基类引用,它看不到派生类对象derived
声明的成员。
8.6.1 虚方法和覆写方法
class Monster
{
public string name;
virtual public void Attack()
{
Console.WriteLine("Simple attack.");
}
}
class Wizard : Monster
{
override public void Attack()
{
Console.WriteLine("Fireball !!!");
}
}
class Program
{
static void Main()
{
Wizard derived = new Wizard();
Monster baseReference = (Monster)derived;
baseReference.Attack(); // 输出 Fireball !!!
}
}
虚方法可以使基类的引用访问“升至”派生类内。需要满足以下条件:
- 派生类的方法和基类的方法有相同的签名和返回类型;
- 基类的方法使用
virtual
标注; - 派生类的方法使用
override
标注。
还需要注意:
- 复写和被覆写的方法必须有相同的可访问性;
- 不能覆写
static
方法或非虚方法; - 方法、属性、索引器、事件都可以被声明为
virtual
和override
。
8.6.2 覆写标记为 override
的方法
class Monster
{
public string name;
virtual public void Attack()
{
Console.WriteLine("Simple attack.");
}
}
class Wizard : Monster
{
override public void Attack()
{
Console.WriteLine("Fireball !!!");
}
}
class WizardElite : Wizard
{
override public void Attack()
{
Console.WriteLine("Splendid fireball !!!");
}
}
class Program
{
static void Main()
{
WizardElite derived = new WizardElite();
Monster baseReference = (Monster)derived;
baseReference.Attack(); // 输出 Splendid fireball !!!
}
}
只要基类的方法是virtual
或者override
,且子类中有同名的override
方法,方法的调用就会沿着派生层次上溯执行,直到标记为override
的最高派生版本。
8.7 构造函数的执行
对象构造的顺序:
- 初始化实例成员;
- 调用基类构造函数;
- 执行实例构造函数的方法体。
8.7.1 构造函数初始化语句
class Monster
{
string name;
int hp, mp;
private Monster()
{
Console.WriteLine("Here comes a new monster!");
}
public Monster(int inputHp, int inputMp) : this()
{
name = "someone";
hp = inputHp;
mp = inputMp;
ShowMonster();
}
public Monster(string input) : this()
{
name = input;
hp = mp = 100;
ShowMonster();
}
private void ShowMonster()
{
Console.WriteLine($"Name: { name }\nHP: { hp }\n, MP: { mp }");
}
}
class Program
{
static void Main()
{
Monster monsterA = new Monster("dragon");
Monster monsterB = new Monster(20, 30);
}
}
有两种形式的构造函数初始化语句:
- 用关键字
base
并指明使用哪一个基类构造函数; - 用关键字
this
并指明应该使用当前类的哪一个构造函数(上面的例子用的就是this
)。
8.7.2 类访问修饰符
- 标记为
public
的类:可以被系统内任何程序集中的代码访问; - 标记为
internal
的类:只能被自己所在的程序集的类看到。这是默认的可访问级别。
8.8 程序集间的继承
略,详见第 22 章。
8.9 成员访问修饰符
修饰符 | 含义 |
---|---|
private | 只在类内部可访问 |
internal | 对该程序集内所有类可访问 |
protected | 对所有继承该类的类可访问 |
protected internal | protected 和internal 的并集 |
public | 任何类均可访问 |
注意:
- 必须对每个成员指定访问级别,否则默认为
private
; - 成员的可访问性不能比它的类高。
8.10 抽象成员
可以被声明为抽象成员的有:方法、属性、事件、索引器。
抽象成员的特征:
- 用
abstract
修饰符标记; - 不能有实现代码块,代码用分号表示。
例如:
abstract public void PrintSomething(string input);
abstract public int MyProperty
{
get;
set;
}
8.11 抽象类
abstract public class Player
{
protected string _name; // 数据成员
abstract public string Name { get; set; } // 抽象属性
abstract public void Attack(); // 抽象方法
public void Greeting() // 普通的非抽象方法
{
Console.WriteLine("Hey, bro");
}
}
public class Human : Player
{
override public string Name // 覆盖抽象属性
{
get { return _name; }
set { _name = value; }
}
public override void Attack() // 覆盖抽象方法
{
Console.WriteLine("Nothing happened...");
}
}
- 不能创建抽象类的实例;
- 抽象类可以派生自另一个抽象类;
- 派生自抽象类的类必须使用
override
关键字实现所有抽象成员,除非派生类也是抽象类。
8.12 密封类
密封类只能用作独立的类,不能用作基类。修饰符是sealed
。
8.13 静态类
所有成员都是静态的,常见用法是包含数学方法和值的数学库。
- 类必须被标记为
static
; - 所有成员必须是静态的;
- 可以有一个静态构造函数,但不能有实例构造函数;
- 静态类是隐式密封的。
8.14 扩展方法
// 没想到比较合适的例子,就用书上的吧
sealed class MyData
{
private double D1, D2, D3;
public MyData(double d1, double d2, double d3)
{
D1 = d1; D2 = d2; D3 = d3;
}
public double Sum()
{
return D1 + D2 + D3;
}
}
static class ExtendMyData
{
static public double Average(this MyData md)
{
return md.Sum() / 3;
}
}
class Program
{
static void Main()
{
MyData md = new MyData(3, 4, 5);
Console.WriteLine($"Sum:\t\t{ md.Sum() }");
Console.WriteLine($"Average:\t{ md.Average() }");
}
}
扩展方法使我可以在:
- 不直接修改原本类的代码;
- 也不继承这个类;
的情况下对原本类的方法进行扩展。
8.15 命名约定
本书遵循的约定:
- 对于类型名称和对外可见的成员名称用 Pascal 大小写;
- 用于局部变量、方法,私有和受保护的字段均用 Camel 大小写。