CodeGym /课程 /C# SELF /C#中的方法重写(override)

C#中的方法重写(override)

C# SELF
第 20 级, 课程 3
可用

1. 引言

假设我们有个动物园动物管理的应用(就是我们前几节课一直在用的那个例子)。我们写了个基类Animal,里面有个MakeSound方法,这样所有动物都能“发出”某种声音。


public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("动物发出声音。");
    }
}
        
带有虚方法的基类

但很快你会发现,狮子和鹦鹉的叫声完全不一样。通用的“动物发出声音”根本不合适。这时候方法重写就派上用场了。你需要让每个子类都能有自己的“声音”!

2. 语法:override怎么用

基本规则

  • 要重写方法,基类里必须用virtual(或者abstract)标记它。
  • 在派生类里,用override关键字修饰同样签名的方法。

例子:狮子和鹦鹉

public class Lion : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("嗷呜呜呜!");
    }
}

public class Parrot : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("傻瓜鹦鹉!");
    }
}

现在,如果你创建一个动物数组,然后对每个调用MakeSound,每个动物都会“说”自己的话:

Animal[] zoo = new Animal[]
{
    new Lion(),
    new Parrot(),
    new Animal()
};

foreach (var animal in zoo)
{
    animal.MakeSound();
}
// 输出:
// 嗷呜呜呜!
// 傻瓜鹦鹉!
// 动物发出声音。
注意:C#会根据对象的真实( run-time)类型调用对应的方法实现,即使变量声明的是 Animal类型!

3. 虚方法表(v-table)

当你用virtual和override时,编译器会为类生成一个特殊的“虚方法表”(v-table)。
通过基类引用调用方法时,CLR会查表:这个方法有没有被子类重写?如果有——就用子类的实现。
这就是“晚绑定”(late binding)的魔法,也叫动态多态。

4. 一起用起来:继续完善我们的应用

再加一个类:

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪汪!");
    }
}

然后在我们的动物管理小程序里用它:

Animal[] zoo = new Animal[]
{
    new Lion(),
    new Parrot(),
    new Dog()
};

foreach (var animal in zoo)
{
    animal.MakeSound();
}

现在代码的可扩展性和可维护性都大大提升了,想加新动物也超方便!

5. 重写属性和带返回值的override

不只是方法可以重写。你也可以重写虚属性和索引器。

public class Animal
{
    public virtual string Name { get; set; } = "动物";
}

public class Lion : Animal
{
    public override string Name { get; set; } = "狮子";
}

这样可以很方便地为不同种类补充专属信息。

6. 用base调用基类实现

有时候你想扩展基类方法的行为,而不是完全替换它。
这时可以在重写方法里用base关键字调用基类实现。

public class Parrot : Animal
{
    public override void MakeSound()
    {
        base.MakeSound(); // "动物发出声音。"
        Console.WriteLine("傻瓜鹦鹉!");
    }
}

这样鹦鹉会先发出通用的“动物园”声音,然后再来一句自己的“傻瓜鹦鹉!”。

7. 实际应用场景:哪里用得上

懂得override机制,在任何稍微复杂点的C#项目里都很重要,尤其是用到OOP的时候。

  • 我们动物园的动物模型? 已经用过了。
  • 自定义UI控件: 你可以重写标准的可视化方法,加上自己的逻辑。
  • 灵活的业务逻辑: 可以在基类里写“骨架”,具体实现交给子类。
  • 测试和mock: 通过继承很容易“替换”方法做单元测试。
  • 插件和扩展: 接口或抽象基类,多种实现——全靠正确的重写机制才能跑起来。

8. sealed override和虚方法链

如果你不想让别人再往下重写你已经重写过的方法,可以用sealed override:

public class Base
{
    public virtual void Foo() { }
}

public class Middle : Base
{
    public sealed override void Foo() { }
}

public class Last : Middle
{
    public override void Foo() {} // 报错——不能再重写了!
}

这样可以在需要的时候“封死”重写链,保证系统的正确性。

9. 新方法(new关键字)

有时候你想在子类里声明一个同名同参数的方法,但基类不是virtual的。
这时可以用new关键字——不过这不是多态,只是“遮盖”:

public class Animal
{
    public void MakeSound()
    {
        Console.WriteLine("我是动物!");
    }
}

public class Cat : Animal
{
    public new void MakeSound()
    {
        Console.WriteLine("我是猫咪!");
    }
}

Animal animal = new Cat();
animal.MakeSound(); // "我是动物!"
Cat cat = new Cat();
cat.MakeSound(); // "我是猫咪!"

这里调用时变量的类型说了算!所以要实现真正的动态多态,还是得用virtual + override组合。

10. 重写方法时的常见错误

错误1: 试图重写一个没用virtual、abstract或override声明的方法。
C#里不能重写普通方法。如果基类方法没加特殊修饰符(virtual、abstract或override),你在子类里写override会编译报错。

错误2: 方法签名不一致。
要想方法真的被重写,名字、返回类型和参数都得和基类方法一模一样。哪怕参数类型不一样,也只是新方法,不是重写。

错误3: 忘了加override修饰符。
如果你写了和基类同签名的方法,但没写override,编译器不会当成重写。这叫方法隐藏(后面会讲)。这时通过基类变量调用方法会得到意外结果——其实是基类方法被调用了,而不是你的。

2
任务
C# SELF, 第 20 级, 课程 3
已锁定
方法重写实现多态
方法重写实现多态
2
任务
C# SELF, 第 20 级, 课程 3
已锁定
通过 `base` 使用基础实现
通过 `base` 使用基础实现
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION