Files
2026-06-21 09:51:55 +08:00

8.6 KiB
Raw Permalink Blame History

多态 vob

概念: 多态是面向对象三大特性之一 指的是:==同一个父类引用,指向不同子类对象时,调用同一个方法会表现出不同结果==

简单理解: 调用方只知道“这是一个动物”,但真正执行时,会根据对象实际是狗、猫还是鸟,执行各自的叫声

vob 可以这样记:

  • vvirtual,父类声明虚方法
  • ooverride,子类重写父类虚方法
  • bbase,子类中调用父类原本的成员

通常用法:

  1. 父类提供统一入口
  2. 子类根据自己的特点重写行为
  3. 调用方只面向父类或接口写代码,不关心具体子类
  4. 减少大量 if/else 类型判断

语法

父类:

class 父类名
{
	public virtual void 方法名()
	{

	}
}

子类:

class 子类名 : 父类名
{
	public override void 方法名()
	{

	}
}

调用:

父类名 变量名 = new 子类名();
变量名.方法名();

基础案例

class Animal
{
	public virtual void Speak()
	{
		Console.WriteLine("动物发出声音");
	}
}

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

class Cat : Animal
{
	public override void Speak()
	{
		Console.WriteLine("猫:喵喵");
	}
}

Animal a1 = new Dog();
Animal a2 = new Cat();

a1.Speak(); // 狗:汪汪
a2.Speak(); // 猫:喵喵

理解:

  • a1a2 的变量类型都是 Animal
  • 但是它们实际指向的对象分别是 DogCat
  • 调用 Speak() 时,会执行实际对象对应的重写版本

这就是多态。


为什么要用多态

不用多态时,代码可能会这样:

void MakeSpeak(Animal animal)
{
	if (animal is Dog)
	{
		Console.WriteLine("狗:汪汪");
	}
	else if (animal is Cat)
	{
		Console.WriteLine("猫:喵喵");
	}
}

问题:

  1. 每增加一个新动物,就要改 MakeSpeak
  2. 判断会越来越多
  3. 行为分散在外部函数里,子类自己的特点没有放回子类中

使用多态后:

void MakeSpeak(Animal animal)
{
	animal.Speak();
}

以后新增子类:

class Bird : Animal
{
	public override void Speak()
	{
		Console.WriteLine("鸟:叽叽");
	}
}

调用方不用改:

MakeSpeak(new Bird());

这就是多态最重要的价值: ==把变化交给子类,把稳定入口留给父类==


virtual

作用: virtual 用在父类成员上,表示这个成员允许被子类重写

class Character
{
	public virtual void Attack()
	{
		Console.WriteLine("角色攻击");
	}
}

注意:

  1. 没有写 virtual 的普通方法,子类不能用 override 重写
  2. virtual 常用于方法、属性、索引器、事件
  3. 构造函数不能是 virtual
  4. 静态方法不能被 override

override

作用: override 用在子类成员上,表示重写父类的虚成员

class Warrior : Character
{
	public override void Attack()
	{
		Console.WriteLine("战士挥剑攻击");
	}
}

重写要求:

  1. 父类成员必须是 virtualabstract 或已经是 override
  2. 子类方法名、参数列表、返回类型要匹配
  3. 访问修饰符通常要保持一致,例如父类是 public,子类也要是 public
  4. override 之后,这个方法仍然可以继续被更下一级子类重写,除非加 sealed

base

作用: base 用在子类内部,表示访问父类成员

常见用途:

  1. 调用父类构造函数
  2. 在重写方法中,先执行父类原本逻辑,再补充子类逻辑
class Character
{
	public virtual void Attack()
	{
		Console.WriteLine("消耗体力");
	}
}

class Warrior : Character
{
	public override void Attack()
	{
		base.Attack();
		Console.WriteLine("战士挥剑攻击");
	}
}

Warrior w = new Warrior();
w.Attack();

输出:

消耗体力
战士挥剑攻击

理解: base.Attack() 不是创建父类对象,而是在子类方法里调用父类原本的 Attack


多态调用看实际对象

多态最关键的一句: ==编译时看变量类型,运行时看实际对象==

Animal animal = new Dog();
animal.Speak();

这里:

  1. 编译时,animal 的类型是 Animal
  2. 所以只能直接调用 Animal 中声明过的成员
  3. 运行时,animal 实际指向的是 Dog
  4. 所以 Speak() 执行的是 Dog 重写后的版本

示例:

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

	public void Bite()
	{
		Console.WriteLine("狗咬人");
	}
}

Animal animal = new Dog();
animal.Speak(); // 可以,多态调用
// animal.Bite(); // 错误,Animal 类型里没有 Bite

如果确实要调用 Dog 独有方法,需要类型转换:

Dog dog = animal as Dog;
if (dog != null)
{
	dog.Bite();
}

但如果代码经常需要这样转,通常要回头检查抽象设计是不是不够好。


override 和 new 的区别

override 是真正的多态重写。

class Father
{
	public virtual void Say()
	{
		Console.WriteLine("Father");
	}
}

class Son : Father
{
	public override void Say()
	{
		Console.WriteLine("Son");
	}
}

Father f = new Son();
f.Say(); // Son

new 是隐藏父类成员,不是真正的多态。

class Father
{
	public void Say()
	{
		Console.WriteLine("Father");
	}
}

class Son : Father
{
	public new void Say()
	{
		Console.WriteLine("Son");
	}
}

Father f = new Son();
f.Say(); // Father

区别:

  1. override:看实际对象,所以会调用 Son.Say()
  2. new:看变量类型,所以 Father f 调用的是 Father.Say()
  3. 想要多态效果,用 virtual + override

常见错误

错误1:父类没写 virtual,子类却写 override

class Animal
{
	public void Speak()
	{
	}
}

class Dog : Animal
{
	public override void Speak()
	{
	}
}

错误原因: 父类的 Speak 不是虚方法,不能被重写


错误2:以为子类独有成员也能通过父类变量直接调用

Animal animal = new Dog();
// animal.Bite(); // 错误

原因: 父类变量只能看到父类声明过的成员


错误3:用 new 代替 override

class Dog : Animal
{
	public new void Speak()
	{
		Console.WriteLine("狗叫");
	}
}

这样只是隐藏,不是真正的多态。


和里氏替换原则的关系

多态能成立的前提是: ==子类真的能当父类来用==

比如:

void MakeSpeak(Animal animal)
{
	animal.Speak();
}

这个函数相信:所有 Animal 都能 Speak()

如果某个子类重写后直接抛异常,或者完全破坏父类原本承诺的行为,就会影响里氏替换原则

所以可以这样联系:

  1. 继承提供父子类型关系
  2. 多态让同一个调用产生不同表现
  3. 里氏替换原则约束子类不要乱改父类承诺

注意

  1. 多态主要依赖 virtual + override
  2. 父类引用可以指向子类对象,这是多态的基础
  3. 调用重写方法时,看的是对象的实际类型
  4. 父类变量只能访问父类中声明过的成员
  5. base 是在子类中访问父类成员,不是创建父类对象
  6. new 是隐藏成员,不等于 override
  7. 如果父类方法不希望再被重写,可以在子类重写时写 sealed override

一句话记忆

==父类定规则,子类改表现,调用时看实际对象。==

再压缩一点: ==virtual 允许改,override 真正改,base 调父类。==


面试/复习时怎么说

可以这样答:

“多态是指同一个父类引用指向不同子类对象时,调用同一个虚方法会执行不同子类的重写实现。
在 C# 中通常通过 virtualoverride 实现,父类用 virtual 声明可重写方法,子类用 override 提供自己的实现。
如果子类重写时还想保留父类原本逻辑,可以用 base 调用父类方法。多态的好处是调用方可以面向父类编程,新增子类时尽量少改原有代码。”


引用:

  1. Microsoft Learn: Polymorphism
  2. Microsoft Learn: virtual (C# Reference)
  3. Microsoft Learn: override (C# Reference)
  4. Microsoft Learn: The base keyword
  5. Microsoft Learn: Knowing When to Use Override and New Keywords

#多态 #核心