繼承和多態
面向對象方法中的繼承體現了現實世界中的“一般特殊關系”。基類代表一般性事物,而派生類是一種特殊的基類,是對基類的補充和細化。不同的派生類執行同一個方法時,會出現不同的行為,這就是多態。
實現繼承
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#只允許一個類僅從一個類繼承,但是一個類可以同時從多個接口繼承。
變量的定義類型和實際類型:
定義變量時的類型叫定義類型。變量被賦予值時的類型叫實際類型。變量的定義類型與實際類型不一定相同。如下
1 object obj1, obj2; 2 3 obj1 = 123; 4 obj2 = "Hello";
obj1和obj2定義類型都為object,但obj1實際類型是int,obj2實際類型是string。變量的類型都可以通過System.Object的GetType方法獲得,GetType返回一個System.Type類型的對象,用於描述變量的類型信息。由於變量可以多次被賦值,所以變量的
實際類型在程序運行過程中是可以動態改變的。如下:
1 static void Main(string[] args)
2 {
3 object obj1, obj2, obj3;
4
5 Console.WriteLine("定義三個object類型變量");
6 obj1 = 123;
7 Console.WriteLine("將obj1賦值為123");
8 obj2 = "Hello";
9 Console.WriteLine("將obj2賦值為\"Hello\"");
10 obj3 = DateTime.Now;
11 Console.WriteLine("將obj3賦值為當前時間");
12 Console.WriteLine("obj1的實際類型為: " + obj1.GetType().ToString());
13 Console.WriteLine("obj2的實際類型為: " + obj2.GetType().ToString());
14 Console.WriteLine("obj3的實際類型為: " + obj3.GetType().ToString());
15
16 obj3 = new int[] { 1, 2, 3 };
17 Console.WriteLine("將obj3賦值為一個整形數組");
18 Console.WriteLine("obj3的實際類型為: " + obj3.GetType().ToString());
19
20 Console.ReadKey();
21 }
運行結果為:
定義三個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[]來使用那麼會報錯,如下:
1 obj3[0] = 1;//報錯
基類和派生類之間的類型轉換
派生類向基類的轉換是安全的,總可以成功;但是基類向派生類轉換時,只有當變量的實際類型是目標類型或或目標類型的派生類時,轉換才能成功,否則會拋出System.InvalidCastException異常。
虛方法和多態
如果基類和派生類都定義了相同的簽名的方法,那麼程序在運行時會調用那個方法呢?如下:
1 class Mammal
2 {
3 public void bark()
4 {
5 Console.WriteLine("Mammal.bark()\t 哺乳動物叫聲各不相同");
6 }
7 }
8
9 class Dog:Mammal
10 {
11 public void bark()
12 {
13 Console.WriteLine("Dog.bark()\t 狗的叫聲汪汪汪");
14 }
15 }
16
17 static void Main(string[] args)
18 {
19 Mammal m = new Mammal();
20 Dog d = new Dog();
21
22 Console.WriteLine("Main 調用 Mammal.bark()");
23 m.bark();
24
25 Console.WriteLine("Main 調用 Dog.bark()");
26 d.bark();
27
28 Console.ReadLine();
29 }
運行結果
Main 調用 Mammal.bark() Mammal.bark() 哺乳動物叫聲各不相同 Main 調用 Dog.bark() Dog.bark() 狗的叫聲汪汪汪
由結果可知調用Mammal類型變量的bark方法時Mammal類的bark方法被執行,調用Dog類的對象的bark方法時,Dog類的bark方法被執行。
如果定義類型與實際類型不一致時,會怎麼樣呢?
1 Mammal m; 2 Dog d = new Dog(); 3 4 m = d; 5 m.bark(); 6 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關鍵字重寫虛方法,可以使程序呈現多態性。
1 class Mammal
2 {
3 public virtual void bark()
4 {
5 Console.WriteLine("Mammal.bark()\t 哺乳動物叫聲各不相同");
6 }
7
8 public void walk()
9 {
10 Console.WriteLine("Mammal.walk()\t 哺乳動物行走");
11 }
12 }
13
14 class Dog:Mammal
15 {
16 public override void bark()
17 {
18 Console.WriteLine("Dog.bark()\t 狗的叫聲汪汪汪");
19 }
20
21 public new void walk()
22 {
23 Console.WriteLine("Dog.walk()\t 狗奔跑很快");
24 }
25 }
26 class Cat:Mammal
27 {
28 public override void bark()
29 {
30 Console.WriteLine("Cat.bark()\t貓的叫聲喵喵喵");
31 }
32
33 public new void walk()
34 {
35 Console.WriteLine("Cat.walk()\t 貓行動敏捷");
36 }
37 }
38 static void Main(string[] args)
39 {
40 Mammal m;
41 Cat c = new Cat();
42 Dog d = new Dog();
43
44 Console.WriteLine("調用bark方法");
45 m = c;
46 m.bark();
47 c.bark();
48
49 m = d;
50 m.bark();
51 d.bark();
52
53 Console.WriteLine("調用walk方法");
54 m = c;
55 m.walk();
56 c.walk();
57
58 m = d;
59 m.walk();
60 d.walk();
61
62
63 Console.ReadLine();
64 }
運行結果
調用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,由於變量
的值不同而表現出不同的行為,形成了多態性。