第 7 课 · 约 14 分钟 · ← 第 6 课

继承与接口

类型之间有两种关系:继承("狗是一种动物——继承它的一切")和接口("机器人会 Speak()——承诺一种能力")。两者存在的目的都是让一份代码通过同一个形状操作许多类型。

virtual / override:基类允许你替换行为

class Animal
{
    public string Name;

    public Animal(string name)
    {
        Name = name;
    }

    public virtual void Speak()          // virtual:子类可以替换
    {
        Console.WriteLine(Name + " makes a sound");
    }
}

class Dog : Animal                        // Dog 继承 Animal
{
    public Dog(string name) : base(name) { }   // base(...):先跑 Animal 的构造器

    public override void Speak()          // 替换它
    {
        Console.WriteLine(Name + " says woof");
    }
}

Animal a = new Dog("Rex");     // 狗是一种动物——这个赋值合法
a.Speak();                     // 分发跟着对象走:Dog 的版本执行
Animal b = new Animal("Generic");
b.Speak();

最后两行是全部要点:变量类型是 Animal,但执行的方法是实际对象上的那个——虚分发。一个遍历混合数组的循环,挨个调 Speak(),每次都叫得恰到好处。

base:在父类的基础上盖楼

base(...) 调父类构造器;base.Method() 调父类版本——标准的"扩展而非替换"形状:

class Animal
{
    public virtual void Speak() { Console.WriteLine("..."); }
}

class Cat : Animal
{
    public override void Speak()
    {
        base.Speak();                  // 先做动物都会做的……
        Console.WriteLine("meow");     // ……再加猫的
    }
}

is:"它真的是那个吗?"——顺便绑定变量

class Animal { }
class Dog : Animal
{
    public void Fetch()
    {
        Console.WriteLine("fetching!");
    }
}

Animal x = new Dog();          // 向上转:没问题,Dog 是 Animal

if (x is Dog d)                // 测试 + 声明一步到位
{
    d.Fetch();                 // 只有狗会捡东西
}

安静的兄弟 as 负责转换、失败时给 null:Dog dd = x as Dog;——然后检查 dd != null。只用来分支用 is;转换后还要用值用 as。

接口:是承诺,不是血缘

interface IGreeter
{
    void Greet();               // 一纸契约:没有方法体,只有承诺
}

class EnglishGreeter : IGreeter
{
    public void Greet() { Console.WriteLine("Hello"); }
}

class SpanishGreeter : IGreeter
{
    public void Greet() { Console.WriteLine("Hola"); }
}

IGreeter[] greeters = { new EnglishGreeter(), new SpanishGreeter() };   // 互不相干的类,同一个承诺
foreach (IGreeter g in greeters)
{
    g.Greet();                 // 各自履行各自的行为
}

两个问候者没有共同基类——接口不是 ancestry,是共同的承诺。这就是为什么 C# 允许一个类实现很多接口、却只能继承一个基类:血缘排他,承诺不排他。

哪个工具,什么时候?

场景用
"X 是一种 Y",共享代码继承(class Dog : Animal)
"X 能做 Z",横跨不同家族接口(: IComparable)
子类微调父类行为virtual + override
子类扩展父类行为override + base.Method()
安全向下转后再用额外成员is(分支)/ as(保留)

现在就练