1. 入门
想象一下,你有个基类Animal,里面有个方法Speak()。默认情况下,所有动物都会输出"某种声音"。你想创建继承类,比如Dog和Cat,让它们说的不是"某种声音",而是它们自己的台词("汪!"和"喵!")。
你当然可以在每个子类里写自己的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,调用的还是动物的方法("某种声音"),不是狗的。类型的坑,真是防不胜防!
GO TO FULL VERSION