繼承和多態
面向對象方法中的繼承體現了現實世界中的“一般特殊關系”。基類代表一般性事物,而派生類是一種特殊的基類,是對基類的補充和細化。不同的派生類執行同一個方法時,會出現不同的行為,這就是多態。
實現繼承
C#中用如下語法實現繼承:
class 派生類:基類 {類的成員}
eg:public class MyButton:System.Windows.Forms.Button {}
C#中所有的類都是直接或間接從System.Object類派生來的,如果定義一個類時沒有指明基類,那麼這個類的基類就是System.Object。.NET Framework中所有的類都是直接或間接派生自System.Object,甚至包括像int、string等簡單的類型也是。
因此C#中所有的類都是直接或間接繼承自System.Object類,從而也都擁有System.Object類中所定義的公共成員。
C#只允許一個類僅從一個類繼承,但是一個類可以同時從多個接口繼承。
變量的定義類型和實際類型:
定義變量時的類型叫定義類型。變量被賦予值時的類型叫實際類型。變量的定義類型與實際類型不一定相同。如下
object obj1, obj2; obj1 = 123; obj2 = "Hello";
obj1和obj2定義類型都為object,但obj1實際類型是int,obj2實際類型是string。變量的類型都可以通過System.Object的GetType方法獲得,GetType返回一個System.Type類型的對象,用於描述變量的類型信息。由於變量可以多次被賦值,所以變量的
實際類型在程序運行過程中是可以動態改變的。如下:
static void Main(string[] args) { object obj1, obj2, obj3; Console.WriteLine("定義三個object類型變量"); obj1 = 123; Console.WriteLine("將obj1賦值為123"); obj2 = "Hello"; Console.WriteLine("將obj2賦值為\"Hello\""); obj3 = DateTime.Now; Console.WriteLine("將obj3賦值為當前時間"); Console.WriteLine("obj1的實際類型為: " + obj1.GetType().ToString()); Console.WriteLine("obj2的實際類型為: " + obj2.GetType().ToString()); Console.WriteLine("obj3的實際類型為: " + obj3.GetType().ToString()); obj3 = new int[] { 1, 2, 3 }; Console.WriteLine("將obj3賦值為一個整形數組"); Console.WriteLine("obj3的實際類型為: " + obj3.GetType().ToString()); Console.ReadKey(); }
運行結果為:
定義三個object類型變量 將obj1賦值為123 將obj2賦值為"Hello" 將obj3賦值為當前時間 obj1的實際類型為: System.Int32 obj2的實際類型為: System.String obj3的實際類型為: System.DateTime 將obj3賦值為一個整形數組 obj3的實際類型為: System.Int32[]
從運行結果來看,3個變量的定義類型都為object,實際類型分別為Int32、String和DateTime,而且obj3實際類型發生了變化,從DateTime變為int[]。
變量只能按照定義的類型來使用。上面例子中obj3定義類型為object,就只能當object類型來使用,雖然後面實際類型為int[],如果把obj3當int[]來使用那麼會報錯,如下:
obj3[0] = 1;//報錯
基類和派生類之間的類型轉換
派生類向基類的轉換是安全的,總可以成功;但是基類向派生類轉換時,只有當變量的實際類型是目標類型或或目標類型的派生類時,轉換才能成功,否則會拋出System.InvalidCastException異常。
虛方法和多態
如果基類和派生類都定義了相同的簽名的方法,那麼程序在運行時會調用那個方法呢?如下:
class Mammal { public void bark() { Console.WriteLine("Mammal.bark()\t 哺乳動物叫聲各不相同"); } } class Dog:Mammal { public void bark() { Console.WriteLine("Dog.bark()\t 狗的叫聲汪汪汪"); } } static void Main(string[] args) { Mammal m = new Mammal(); Dog d = new Dog(); Console.WriteLine("Main 調用 Mammal.bark()"); m.bark(); Console.WriteLine("Main 調用 Dog.bark()"); d.bark(); Console.ReadLine(); }
運行結果
Main 調用 Mammal.bark() Mammal.bark() 哺乳動物叫聲各不相同 Main 調用 Dog.bark() Dog.bark() 狗的叫聲汪汪汪
由結果可知調用Mammal類型變量的bark方法時Mammal類的bark方法被執行,調用Dog類的對象的bark方法時,Dog類的bark方法被執行。
如果定義類型與實際類型不一致時,會怎麼樣呢?
Mammal m; Dog d = new Dog(); m = d; m.bark(); d.bark();
運行結果
Mammal.bark() 哺乳動物叫聲各不相同 Dog.bark() 狗的叫聲汪汪汪
從運行結果可以看出,雖然m和d是同一個對象,但由於定義對象不同,掉用bark執行的代碼也不一樣。bark方法實際執行的代碼是由定義類型決定的。所以m.bark()調用Mammal的bark方法,d.bark()調用Dog的bark方法。
在很多時候,開發人員並不希望程序這樣運行,而是希望程序能夠根據變量的實際類型來調用相應的方法。這樣對於同一個Mammal類型的變量m,當其實際類型為不同的派生類時,調用m.bark()方法會產生不同的行為,這就是多態。
當基類和派生類都定義了相同簽名的方法時,C#允許開發人員明確指定哪個方法應該被調用。是根據定義類型調用方法還是根據實際類型調用方法,C#通過虛方法、方法重寫和方法隱藏實現這個功能。
如果想讓程序在運行時根據變量的定義類型來決定調用那個方法,可以通過方法隱藏來實現;如果想讓程序實現多態性,即在運行時根據變量的實際類型調用相應的方法,那麼可以通過虛方法和方法重寫實現。
定義方法時使用new關鍵字可以隱藏基類具有相同簽名的方法,語法如下:
訪問修飾符 new 返回值類型 方法名(參數列表){方法體}
上述代碼預定一個普通方法的唯一區別是多了一個new關鍵字,new關鍵字表明這個方法將隱藏基類中相同簽名的方法。new關鍵字可以放在訪問修飾符的前面或後面都可以。
使用virtual關鍵字可以定義一個虛方法,虛方法可以在派生類中被重寫。定義虛方法語法如下:
訪問修飾符 virtual 返回值類型 方法名(參數列表) {方法體}
派生類使用override關鍵字重寫基類中的虛方法,語法如下:
訪問修飾符 override 返回值類型 方法名(參數列表) {方法體}
在基類中使用virtual關鍵字定義虛方法,在派生類中使用override關鍵字重寫虛方法,可以使程序呈現多態性。
class Mammal { public virtual void bark() { Console.WriteLine("Mammal.bark()\t 哺乳動物叫聲各不相同"); } public void walk() { Console.WriteLine("Mammal.walk()\t 哺乳動物行走"); } } class Dog:Mammal { public override void bark() { Console.WriteLine("Dog.bark()\t 狗的叫聲汪汪汪"); } public new void walk() { Console.WriteLine("Dog.walk()\t 狗奔跑很快"); } } class Cat:Mammal { public override void bark() { Console.WriteLine("Cat.bark()\t貓的叫聲喵喵喵"); } public new void walk() { Console.WriteLine("Cat.walk()\t 貓行動敏捷"); } } static void Main(string[] args) { Mammal m; Cat c = new Cat(); Dog d = new Dog(); Console.WriteLine("調用bark方法"); m = c; m.bark(); c.bark(); m = d; m.bark(); d.bark(); Console.WriteLine("調用walk方法"); m = c; m.walk(); c.walk(); m = d; m.walk(); d.walk(); Console.ReadLine(); }
運行結果
調用bark方法 Cat.bark() 貓的叫聲喵喵喵 Cat.bark() 貓的叫聲喵喵喵 Dog.bark() 狗的叫聲汪汪汪 Dog.bark() 狗的叫聲汪汪汪 調用walk方法 Mammal.walk() 哺乳動物行走 Cat.walk() 貓行動敏捷 Mammal.walk() 哺乳動物行走 Dog.walk() 狗奔跑很快()
由運行結果可以看出用new關鍵字進行方法隱藏後,被調用的方法由變量的定義類型決定。雖然m實際是Dog類(或Cat類)的實例,但是由於m被定義成一個Mammal類型的變量,所以當調用m.walk()方法時,總是調用Mammal類的walk方法,
而不會調用Cat或Dog類的walk方法。
對於使用virtual和override關鍵字聲明的方法,再調用時由變量的實際類型決定調用那個類的相應方法。m先後被賦予Cat類型和Dog類型的值,在調用bark方法時,先後調用了Cat類的bark方法和Dog類的bark方法。同一段代碼m.bark,由於變量
的值不同而表現出不同的行為,形成了多態性。