# 多态 vob 概念: 多态是面向对象三大特性之一 指的是:==同一个父类引用,指向不同子类对象时,调用同一个方法会表现出不同结果== 简单理解: 调用方只知道“这是一个动物”,但真正执行时,会根据对象实际是狗、猫还是鸟,执行各自的叫声 `vob` 可以这样记: - `v`:`virtual`,父类声明虚方法 - `o`:`override`,子类重写父类虚方法 - `b`:`base`,子类中调用父类原本的成员 通常用法: 1. 父类提供统一入口 2. 子类根据自己的特点重写行为 3. 调用方只面向父类或接口写代码,不关心具体子类 4. 减少大量 `if/else` 类型判断 ---- # 语法 父类: ```csharp class 父类名 { public virtual void 方法名() { } } ``` 子类: ```csharp class 子类名 : 父类名 { public override void 方法名() { } } ``` 调用: ```csharp 父类名 变量名 = new 子类名(); 变量名.方法名(); ``` ---- # 基础案例 ```csharp 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(); // 猫:喵喵 ``` 理解: - `a1` 和 `a2` 的变量类型都是 `Animal` - 但是它们实际指向的对象分别是 `Dog` 和 `Cat` - 调用 `Speak()` 时,会执行实际对象对应的重写版本 这就是多态。 ---- # 为什么要用多态 不用多态时,代码可能会这样: ```csharp void MakeSpeak(Animal animal) { if (animal is Dog) { Console.WriteLine("狗:汪汪"); } else if (animal is Cat) { Console.WriteLine("猫:喵喵"); } } ``` 问题: 1. 每增加一个新动物,就要改 `MakeSpeak` 2. 判断会越来越多 3. 行为分散在外部函数里,子类自己的特点没有放回子类中 使用多态后: ```csharp void MakeSpeak(Animal animal) { animal.Speak(); } ``` 以后新增子类: ```csharp class Bird : Animal { public override void Speak() { Console.WriteLine("鸟:叽叽"); } } ``` 调用方不用改: ```csharp MakeSpeak(new Bird()); ``` 这就是多态最重要的价值: ==把变化交给子类,把稳定入口留给父类== ---- # virtual 作用: `virtual` 用在父类成员上,表示这个成员允许被子类重写 ```csharp class Character { public virtual void Attack() { Console.WriteLine("角色攻击"); } } ``` 注意: 1. 没有写 `virtual` 的普通方法,子类不能用 `override` 重写 2. `virtual` 常用于方法、属性、索引器、事件 3. 构造函数不能是 `virtual` 4. 静态方法不能被 `override` ---- # override 作用: `override` 用在子类成员上,表示重写父类的虚成员 ```csharp class Warrior : Character { public override void Attack() { Console.WriteLine("战士挥剑攻击"); } } ``` 重写要求: 1. 父类成员必须是 `virtual`、`abstract` 或已经是 `override` 2. 子类方法名、参数列表、返回类型要匹配 3. 访问修饰符通常要保持一致,例如父类是 `public`,子类也要是 `public` 4. `override` 之后,这个方法仍然可以继续被更下一级子类重写,除非加 `sealed` ---- # base 作用: `base` 用在子类内部,表示访问父类成员 常见用途: 1. 调用父类构造函数 2. 在重写方法中,先执行父类原本逻辑,再补充子类逻辑 ```csharp 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(); ``` 输出: ```text 消耗体力 战士挥剑攻击 ``` 理解: `base.Attack()` 不是创建父类对象,而是在子类方法里调用父类原本的 `Attack` ---- # 多态调用看实际对象 多态最关键的一句: ==编译时看变量类型,运行时看实际对象== ```csharp Animal animal = new Dog(); animal.Speak(); ``` 这里: 1. 编译时,`animal` 的类型是 `Animal` 2. 所以只能直接调用 `Animal` 中声明过的成员 3. 运行时,`animal` 实际指向的是 `Dog` 4. 所以 `Speak()` 执行的是 `Dog` 重写后的版本 示例: ```csharp 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` 独有方法,需要类型转换: ```csharp Dog dog = animal as Dog; if (dog != null) { dog.Bite(); } ``` 但如果代码经常需要这样转,通常要回头检查抽象设计是不是不够好。 ---- # override 和 new 的区别 `override` 是真正的多态重写。 ```csharp 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` 是隐藏父类成员,不是真正的多态。 ```csharp 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` ```csharp class Animal { public void Speak() { } } class Dog : Animal { public override void Speak() { } } ``` 错误原因: 父类的 `Speak` 不是虚方法,不能被重写 ---- 错误2:以为子类独有成员也能通过父类变量直接调用 ```csharp Animal animal = new Dog(); // animal.Bite(); // 错误 ``` 原因: 父类变量只能看到父类声明过的成员 ---- 错误3:用 `new` 代替 `override` ```csharp class Dog : Animal { public new void Speak() { Console.WriteLine("狗叫"); } } ``` 这样只是隐藏,不是真正的多态。 ---- # 和里氏替换原则的关系 多态能成立的前提是: ==子类真的能当父类来用== 比如: ```csharp 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# 中通常通过 `virtual` 和 `override` 实现,父类用 `virtual` 声明可重写方法,子类用 `override` 提供自己的实现。 如果子类重写时还想保留父类原本逻辑,可以用 `base` 调用父类方法。多态的好处是调用方可以面向父类编程,新增子类时尽量少改原有代码。” ---- 引用: 1. Microsoft Learn: [Polymorphism](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/object-oriented/polymorphism) 2. Microsoft Learn: [virtual (C# Reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/virtual) 3. Microsoft Learn: [override (C# Reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/override) 4. Microsoft Learn: [The base keyword](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/base) 5. Microsoft Learn: [Knowing When to Use Override and New Keywords](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/knowing-when-to-use-override-and-new-keywords) #多态 #核心