什么是多態
公司最近為了陶冶情操,養了幾種動物(Animal),有貓(Cat)、狗(Dog)、羊(Sheep),這些動物都有共同的特性,會吃(Eat)、會叫(Shout),但是它們吃的不同,叫的也不同。既然這樣,我們能不能設計一個動物類(Animal)和它的成員(Eat方法、Shout方法)來表示這些動物的共同特征,而當我們關注貓時,貓來實現這兩個成員(吃魚、喵喵叫);當我們關注狗時,狗來實現這兩個成員(吃肉和汪汪叫)。
上述例子就是一個典型的多態,就是父類的一些成員,子類繼承后去重寫從而實現不同的功能。
定義:同一操作作用於不同的對象,可以有不同的解釋,產生不同的執行結果。這就是多態,這種特性稱為多態性。
多態的優缺點
好處:
簡化了編程接口。它容許在類和類之間重用一些習慣性的命名,而不用為每一個新加的函數命名一個新名字。
簡化代碼。如果函數/方法參數中使用的是父類的類型,可以傳入父類、子類的對象
局限性:
父類類型的變量,不能直接調用子類特有的方法。必須強轉為子類類型的變量,才能直接調用子類特有的方法
注意:子類中如果重寫了父類的方法(多態),那么父類中的這個方法將不會再調用。
多態的分類
多態性分為兩種,一種是編譯時的多態性,一種是運行時的多態性。
編譯時的多態性(重載):編譯時的多態性是通過重載來實現的。對於非虛的成員來說,系統在編譯時,根據傳遞的參數、返回的類型等信息決定實現何種操作。
運行時的多態性(重寫):運行時的多態性就是指直到系統運行時,才根據實際情況決定實現何種操作。C#中運行時的多態性是通過覆寫虛成員實現。
多態的實現
編譯時多態:重載(overload)
重載(overload):重載指的是同一個類中有兩個或多個名字相同但是參數(參數簽名)不同的方法,(注:返回值不能區別函數是否重載),重載沒有關鍵字。
注意:
A.從重載的定義來看,重載是一種編譯時多態
B.重載不需要事先定義可重載的方法,即沒有關鍵字
C.重載只是針對一個類內部的幾個參數不同,名稱相同的方法。
我們還是用那幾只陶冶情操的動物來示例說明,代碼如下:
/// <summary>
/// 狗(多態:重載事例) /// </summary>
class Dog { /// <summary>
/// 叫 /// </summary>
public void Shout() { Console.WriteLine("汪!"); } /// <summary>
/// 叫(重載方法) /// </summary>
public void Shout(int count) { int i = 0; string shout = ""; do { shout += "汪!"; i++; } while (i <= count); Console.WriteLine(shout); } }
//調用
Dog dog = new Dog(); dog.Shout(); dog.Shout(5);
運行時多態:重寫
重寫有兩種,一種是override修飾符,另一種使用new修飾符,下面會舉例說明兩種重寫的使用方法和異同。
重寫(override):也稱過載,重寫是指子類對父類中虛函數或抽象函數的“覆蓋”(這也就是有些書將過載翻譯為覆蓋的原因),但是這種“覆蓋”和用new關鍵字來覆蓋是有區別的。

/// <summary>
/// 動物類(父類) /// </summary>
class Animal { /// <summary>
/// 名字 /// 說明:類和子類可訪問 /// </summary>
protected string name; /// <summary>
/// 構造函數 /// </summary>
/// <param name="name"></param>
public Animal(string name) { this.name=name; } /// <summary>
/// 名字(虛屬性) /// </summary>
public virtual string MyName { get { return this.name; } } /// <summary>
/// 吃(虛方法) /// </summary>
public virtual void Eat() { Console.WriteLine("我會吃!"); } /// <summary>
/// 叫(虛方法) /// </summary>
public virtual void Shout() { Console.WriteLine("我會叫!"); } } /// <summary>
/// 狗(子類) /// </summary>
class Dog:Animal { string myName; public Dog(string name): base(name) { myName = name; } /// <summary>
/// 名字(重寫父類屬性) /// </summary>
public override string MyName { get { return "我是:狗狗,我叫:"+this.name; } } /// <summary>
/// 吃(重寫父類虛方法) /// </summary>
public override void Eat() { Console.WriteLine("我喜歡吃肉!"); } /// <summary>
/// 叫(重寫父類方法) /// </summary>
public override void Shout() { Console.WriteLine("汪!汪!汪!"); } }
//調用方法
Animal dog = new Dog("旺財"); string myName=dog.MyName; Console.WriteLine(myName); dog.Eat(); dog.Shout(); //運行結果如下:
我是:狗狗,我叫:旺財 我喜歡吃肉! 汪!汪!汪!
重寫(new)
new:覆蓋指的是不同類中(基類或派生類)有兩個或多個返回類型、方法名、參數都相同,但是方法體不同的方法。但是這種覆蓋是一種表面上的覆蓋,所以也叫隱藏,被覆蓋的父類方法是可以調用得到的。

/// <summary>
/// 動物類(父類) /// </summary>
class Animal { /// <summary>
/// 名字 /// 說明:類和子類可訪問 /// </summary>
protected string name; /// <summary>
/// 構造函數 /// </summary>
/// <param name="name"></param>
public Animal(string name) { this.name=name; } /// <summary>
/// 名字(虛屬性) /// </summary>
public virtual string MyName { get { return this.name; } } /// <summary>
/// 吃(虛方法) /// </summary>
public virtual void Eat() { Console.WriteLine("我會吃!"); } /// <summary>
/// 叫(虛方法) /// </summary>
public virtual void Shout() { Console.WriteLine("我會叫!"); } } /// <summary>
/// 狗(子類) /// </summary>
class Dog:Animal { string myName; public Dog(string name): base(name) { myName = name; } /// <summary>
/// 名字(重寫父類屬性) /// </summary>
public override string MyName { get { return "我是:狗狗,我叫:"+this.name; } } /// <summary>
/// 吃(重寫父類虛方法) /// </summary>
public new void Eat() { Console.WriteLine("我喜歡吃肉!"); } /// <summary>
/// 叫(重寫父類方法) /// </summary>
public new void Shout() { Console.WriteLine("汪!汪!汪!"); } }
//調用方法 使用new重寫,則只調用父類的方法
Animal dog = new Dog("旺財"); string myName=dog.MyName; Console.WriteLine(myName); dog.Eat(); dog.Shout(); //執行結果如下:
我是:狗狗,我叫:旺財 我會吃! 我會叫!
如下改一下調用方法:
//調用方法
Dog dog = new Dog("旺財"); string myName=dog.MyName; Console.WriteLine(myName); dog.Eat(); dog.Shout(); //執行結果如下:
我是:狗狗,我叫:旺財! 我愛吃肉! 汪!汪!汪!
可以看出,當派生類Dog的Eat()方法使用new修飾時,Dog的對象轉換為Animal對象后,調用的是Animal類中的Eat()方法。其實可以理解為,使用new關鍵字后,使得Dog中的Eat()方法和Animal中的Eat()方法成為毫不相關的兩個方法,只是它們的名字碰巧相同而已。所以, Animal類中的Eat()方法不管用還是不用virtual修飾,也不管訪問權限如何,或者是沒有,都不會對Dog的Eat()方法產生什么影響(只是因為使用了new關鍵字,如果Dog類沒用從Animal類繼承Eat()方法,編譯器會輸出警告)。
嚴格的說,不能說通過使用new來實現多態,只能說在某些特定的時候碰巧實現了多態的效果。
要點:
1. 多態是面向對象的重要特性之一,指同一操作作用於不同的對象,可以有不同的解釋,產生不同的執行結果。
2. 多態分為兩種:一種是編譯時多態,使用重載實現;另一種是運行時多態,使用重寫實現;
3. 重寫有兩種,一種使用override關鍵詞,另一種使用new關鍵詞
4. new重寫實際上是對父類方法的隱藏,被覆蓋的父類方法可以調用得到。因此new可以重寫(或說是隱藏)的父類方法不一定要定義為虛方法或抽象方法。只是如果父類方法是虛方法或抽象方法時會覆蓋父類方法,如果不是,則隱藏。
5. 當用子類創建父類的時候,如C1 c = new C2(),重寫會改變父類的功能,即調用子類的功能;而覆蓋(new)不會,仍然調用父類功能
6. 重載和覆蓋的發生條件:
重載,必然發生在一個類中,函數名相同,參數類型或者順序不同構成重載,與返回類型無關
重寫,必然發生在基類和派生類中,其類函數用virtual修飾,派生類用override修飾
覆蓋,在子類中寫一個和基類一樣名字(參數不同也算)的非虛函數,會讓基類中的函數被隱藏,編譯后會提示要求使用New關鍵字 new 修飾隱藏,在子類中可以通過new 隱藏父類的方法
7. virtual修飾符不能與private、static、abstract或者override修飾符同時使用
8. override修飾符不能與new、static或者virtual修飾符同時使用,並且重寫方法只能用於重寫基類中的虛方法
f.new覆蓋與重寫、重載的區別:
當子類與父類的參數不同時
當基類函數不是虛函數時,基類函數將被隱藏。(因為子類和基類不在同一范圍內,所以不是重載)
當基類函數是虛函數時,基類函數將被隱藏。(因為子類和基類不在同一范圍內,所以不是重載;因為參數不同,所以不是重寫)
當子類與父類的參數相同時
當基類函數不是虛函數時,基類函數將被隱藏。(因為子類和基類不在同一范圍內,所以不是重載,因為基類不是虛函數,所以是隱藏不是重寫)
當基類函數是虛函數時,基類函數將被覆蓋。(因為子類和基類不在同一范圍內,所以不是重載)
普通類多態Eg:

using System; namespace Traffic { class Animal { public virtual void Breed()//定義 public 修飾的 virtual 虛方法
{ Console.WriteLine("動物需要交配來繁殖后代"); } protected virtual void Breed2()//定義 protected 修飾的 virtual 虛方法
{ Console.WriteLine("動物需要交配來繁殖后代"); } public void DoBreed() { this.Breed2(); } } class Mantis : Animal //繼承自動物類的螳螂類
{ public override void Breed()//override重寫父類Animal類型定義的繁殖方法
{ Console.WriteLine("母螳螂吃掉公螳螂繁殖后代"); } protected override void Breed2()//override重寫父類Animal類型定義的繁殖方法
{ Console.WriteLine("母螳螂吃掉公螳螂繁殖后代"); } } class Chook : Animal //繼承自動物類的雞類
{ public override void Breed()//override重寫父類Animal類型定義的繁殖方法
{ Console.WriteLine("雞生蛋來繁殖后代"); } protected override void Breed2()//override重寫父類Animal類型定義的繁殖方法
{ Console.WriteLine("雞生蛋來繁殖后代"); } } class Program { static void Main(string[] args) { Animal animal = new Animal(); animal.Breed(); Mantis mantis = new Mantis();//創建 mantis 對象
if (mantis is Animal)//is關鍵字判斷對象 mantis 是否屬於 Animal 類
{ Animal mantisAnimal = mantis as Animal;//as 關鍵字將 mantis 對象轉換為 Animal 類型對象
mantisAnimal.Breed();//調用Animal類型對象的 Bread 方法
} Chook chook = new Chook(); if (chook is Animal) { Animal chookAnimal = chook as Animal; chook.Breed(); } Console.WriteLine(Environment.NewLine);//空行間隔
Animal animal2 = new Animal(); //animal2.DoBreed();
Breed(animal2); Mantis mantis2 = new Mantis(); //mantis2.DoBreed();
Breed(mantis2); Chook chook2 = new Chook(); chook2.DoBreed(); //Breed(chook2);
} static void Breed(Animal animal)//定義參數類型為父類型的 Breed 方法
{ animal.DoBreed(); } } }
代碼分析:
1.定義 Mantis 和 Chook 類的時候重寫了 Breed 方法
2.Animal mantisAnimal = mantis as Animal;//as 關鍵字將 mantis 對象轉換為 Animal 類型對象,此時並未創建新的對象,只是將原來的對象轉換了類型,mantis 和 mantisAnimal 指向同一個對象
3.代碼示例是以兩種不同的方式,一種是簡單示例,一種是復雜示例
運行結果:
抽象類多態:
抽象類不能實例化,抽象方法只能在抽象類中定義,在定義抽象類的子類時必須將抽象類中定義的抽象方法重寫。
抽象類多態Eg:

using System; namespace Traffic { abstract class Animal { public abstract void Breed();//定義 public 修飾的 abstract 抽象方法
protected abstract void Breed2();//定義 protected 修飾的 abstract 虛方法
public void DoBreed() { this.Breed2(); } } class Mantis : Animal //繼承自動物類的螳螂類
{ public override void Breed()//override重寫父類Animal類型定義的繁殖方法
{ Console.WriteLine("母螳螂吃掉公螳螂繁殖后代"); } protected override void Breed2()//override重寫父類Animal類型定義的繁殖方法
{ Console.WriteLine("母螳螂吃掉公螳螂繁殖后代"); } } class Chook : Animal //繼承自動物類的雞類
{ public override void Breed()//override重寫父類Animal類型定義的繁殖方法
{ Console.WriteLine("雞生蛋來繁殖后代"); } protected override void Breed2()//override重寫父類Animal類型定義的繁殖方法
{ Console.WriteLine("雞生蛋來繁殖后代"); } } class Program { static void Main(string[] args) { Mantis mantis = new Mantis();//創建 mantis 對象
if (mantis is Animal)//is關鍵字判斷對象 mantis 是否屬於 Animal 類
{ Animal mantisAnimal = mantis as Animal;//as 關鍵字將 mantis 對象轉換為 Animal 類型對象
mantisAnimal.Breed();//調用Animal類型對象的 Bread 方法
} Chook chook = new Chook(); if (chook is Animal) { Animal chookAnimal = chook as Animal; chook.Breed(); } Console.WriteLine(Environment.NewLine);//空行間隔
Mantis mantis2 = new Mantis(); //mantis2.DoBreed();
Breed(mantis2); Chook chook2 = new Chook(); chook2.DoBreed(); //Breed(chook2);
} static void Breed(Animal animal)//定義參數類型為父類型的 Breed 方法
{ animal.DoBreed(); } } }
運行結果:
接口多態Eg:

using System; namespace Traffic { interface IAnimal { void Breed();//聲明接口成員
} class Mantis : IAnimal //繼承自接口的螳螂類
{ public void Breed()//類繼承接口后,必須將接口聲明的全部成員都實現
{ Console.WriteLine("母螳螂吃掉公螳螂繁殖后代"); } } class Chook : IAnimal //繼承自接口的雞類
{ public void Breed() { Console.WriteLine("雞生蛋來繁殖后代"); } } class Program { static void Main(string[] args) { Mantis mantis = new Mantis();//創建 mantis 對象
if (mantis is IAnimal)//is關鍵字判斷對象 mantis 是否屬於 Animal 類
{ IAnimal imantis = mantis as IAnimal;//as 關鍵字將 mantis 對象轉換為 Animal 類型對象
imantis.Breed();//調用Animal類型對象的 Bread 方法
} IAnimal ichook = new Chook();//創建雞對象后直接轉換為 IAnimal 接口類型變量 ichook
ichook.Breed(); Console.WriteLine(Environment.NewLine);//空行間隔
Mantis mantis2 = new Mantis(); Breed(mantis2); Chook chook2 = new Chook(); Breed(chook2); Console.ReadLine(); } static void Breed(IAnimal animal)//定義參數類型為父類型的 Breed 方法
{ animal.Breed(); } } }
運行結果:
原文鏈接:https://www.cnblogs.com/qinyi173/p/4685947.html
原文鏈接:http://blog.sina.com.cn/s/blog_bc7f750001016gg4.html