CodeGym /课程 /C# SELF /C#中的虚方法:多态和重写

C#中的虚方法:多态和重写

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

1. 入门

想象一下,你有个基类Animal,里面有个方法Speak()。默认情况下,所有动物都会输出"某种声音"。你想创建继承类,比如DogCat,让它们说的不是"某种声音",而是它们自己的台词("汪!""喵!")。

你当然可以在每个子类里写自己的Speak(),但如果你用Animal[]这种集合,调用animal.Speak()的时候,还是会走Animal的方法,而不是狗或猫的——因为C#默认看的是变量类型,不是它后面实际指向的对象。

举个手撸的例子:


Animal dog = new Dog();
dog.Speak(); // 哎,输出啥?

默认会输出"某种声音",而不是"汪!",即使Dog里有自己的Speak()。咋办?你得在基类里把方法声明成virtual,在子类里用override重写。

啥叫虚方法?

virtual方法就是在基类里用 virtual修饰符声明的方法,目的是让子类能用 override来重写它。

通过基类引用调用这种方法时,会执行“真实”对象自己的方法——就像现实生活:你养了一只动物,它其实是只猫,那它就会说"喵!",而不是"某种声音"

虚方法是C#多态的核心。

2. 虚方法的声明

怎么声明虚方法

在基类里用virtual关键字声明方法:


public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("某种声音");
    }
}
在基类里声明虚方法

在子类里用override关键字:


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

public class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("喵!");
    }
}
在子类里重写虚方法

例子:多态演示


// 比如在 Program.cs 文件里

// 基类 Animal,带虚方法 Speak
public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("某种声音");
    }
}

// Dog 类,重写 Speak 行为
public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("汪!");
    }
}

// Cat 类,也重写 Speak
public class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("喵!");
    }
}

public static class Program
{
    public static void Main()
    {
        Animal[] animals = new Animal[]
        {
            new Animal(),
            new Dog(),
            new Cat()
        };

        foreach (Animal animal in animals)
        {
            animal.Speak(); // 输出:"某种声音", "汪!", "喵!"
        }
    }
}

看见没,虚方法就是这么神奇!即使变量类型是Animal,也会调用具体实现的那个方法。

3. “底层”发生了啥:虚表机制

机制简述

当你把方法声明成虚方法时,编译器会给每个类型加一个专门的“虚方法表”(就像每道菜有自己的菜单)。调用这种方法时,C#不看变量类型,而是看引用后面实际是什么——然后“塞”进去对应类型的方法。

可以说,C#会自动把通用的配方换成你自定义的那份,只要类里有自己的实现。

类比:合同里“补充条款”

想象你有个租房合同(基类),里面写着租客默认每月交固定钱(PayRent()方法)。但你——狡猾的房东——允许在合同附件里写特殊条款(虚方法)。如果某个租客(子类)用了这个权利,他就会写自己的override,以后收钱方式就变了。

4. 什么时候用虚方法?

  • 如果基类的方法对大多数子类都适用,但有些子类需要特殊行为。
  • 当你设计类层次结构,打算让部分逻辑可扩展或可变时。
  • 开发灵活架构,比如业务逻辑里,不同操作类型需要不同表现时。

实际项目里,虚方法是模板方法(Template Method)、策略(Strategy)等模式的基础,基本是OOP的一半灵魂。

和普通方法的对比

普通方法 虚方法
能否重写 不能 能(override
调用方式 看变量类型 看“真实”对象类型
多态 没有

常见问题

  • 构造函数能虚吗?
    不能!构造函数永远不会是虚的——它们只看对象类型。
  • 虚方法能是静态的吗?
    不能,只有实例方法能虚。静态方法不参与多态,因为没有对象——那谁来重写?
  • 字段能虚吗?
    不能,只有方法、属性和事件能虚。

5. 实战:扩展“动物世界”应用

在我们的学习项目里,你已经做了个简单的动物继承体系。现在来给动物们加点新虚方法,让不同动物吃饭方式都不一样!

带注释的代码例子


// 在基类里加虚方法 Eat
public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("某种声音");
    }

    public virtual void Eat()
    {
        Console.WriteLine("吃通用食物。");
    }
}

// Dog 里重写 Eat
public class Dog : Animal
{
    public override void Speak() => Console.WriteLine("汪!");
    public override void Eat() => Console.WriteLine("吃狗粮。");
}

// Cat 也有自己的行为
public class Cat : Animal
{
    public override void Speak() => Console.WriteLine("喵!");
    public override void Eat() => Console.WriteLine("吃鱼。");
}

public static class Program
{
    public static void Main()
    {
        Animal[] animals = new Animal[]
        {
            new Animal(),
            new Dog(),
            new Cat()
        };
        foreach (var animal in animals)
        {
            animal.Speak();
            animal.Eat();
            Console.WriteLine("---");
        }
    }
}

好了,现在每只猫猫狗狗都按自己的方式吃饭!而基类Animal还在吃点奇怪的东西,像个有“默认值”的功能。

6. 常见错误和细节

  • 可能会忘了在子类里写override,结果还是用基类的方法。
  • 或者反过来,忘了在基类里写virtual,那子类就不能写override了。

virtual不能写override

C#编译器不让你重写一个没声明成虚方法(或抽象方法)的基类方法。

为啥要override

override明确告诉编译器和看代码的人:“我不是随便写个新方法——我是有意识地改基类的实现。”这样能防止你本来想“重写”,结果只是写了个签名一样的新方法。

如果忘了override,直接写了新方法

public class Dog : Animal
{
    public void Speak()
    {
        Console.WriteLine("汪!");
    }
}
不是重写!多态不起作用。

但这不是重写!如果你用Animal类型的变量指向Dog,调用的还是动物的方法("某种声音"),不是狗的。类型的坑,真是防不胜防!

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION