C# Polymorphism - Compile Time Polymorphism

Posted by Ryan Tseng on 2018-10-28

上一篇文章介紹了所謂的執行時期的多型,第二篇要來講的就是編譯時期的多型

在理解這個概念之前,我們要先知道 virtual, abstract 這幾個關鍵字的作用

  • virtual
    • 表示衍生類別能夠覆寫基底類別的方法
  • abstract
    • class
      • 無法拿來建立物件的實體
      • 需要類別去繼承它 (和 sealed 相反,sealed 是防止其它類別繼承某個類別)
      • 衍生類別要包含所有抽象方法、的實作 (因為抽象方法本身就沒有實作)
    • method
      • 抽象方法不定義方法內容,所以是一種隱含的 virtual 方法 (表示需要透過衍生類別來實作這些抽象的方法)
      • 抽象類別才可以放抽象方法
      • 抽象方法不能再使用 virtual,也不能使用 static 關鍵字

多型的特性

直接拿官方文件的例子來說明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class Shape
{
// A few example members
public int X { get; private set; }
public int Y { get; private set; }
public int Height { get; set; }
public int Width { get; set; }

// Virtual method
public virtual void Draw()
{
Console.WriteLine("Performing base class drawing tasks");
}
}

class Circle : Shape
{
public override void Draw()
{
// Code to draw a circle...
Console.WriteLine("Drawing a circle");
base.Draw();
}
}
class Rectangle : Shape
{
public override void Draw()
{
// Code to draw a rectangle...
Console.WriteLine("Drawing a rectangle");
base.Draw();
}
}
class Triangle : Shape
{
public override void Draw()
{
// Code to draw a triangle...
Console.WriteLine("Drawing a triangle");
base.Draw();
}
}

class Program
{
static void Main(string[] args)
{
// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used whereever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
var shapes = new List<Shape>
{
new Rectangle(),
new Triangle(),
new Circle()
};

// Polymorphism at work #2: the virtual method Draw is
// invoked on each of the derived classes, not the base class.
foreach (var shape in shapes)
{
shape.Draw();
}

// Keep the console open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}

}

可以看到 三角形、圓形、四邊形,都繼承自 Shape 的基底類別,裡面定義了一些基本的屬性以及一個虛擬方法稱為 Draw()

主程式中定義了一個 List<Shape>() 來裝這些衍生類別的實體,並且使用 foreach 來跑 Shape 中的 Draw 方法,我們可以發現即便它呼叫的是 Shape 基底類別的方法,它依然都還是執行衍生類別的 Draw 方法

1
2
3
4
5
6
Shape s1 = new Triangle();
s1.Draw();
Shape s2 = new Circle();
s2.Draw();
Shape s3 = new Rectangle();
s3.Draw();

或是我們這樣看比較能夠顯現出 多型 這件事情,雖然我們都是用 Shape 類別來裝這些衍生類別,但是實際呼叫的時候也都還是會執行覆寫後的 Draw 方法的內容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void Main()
{
Circle c = new Circle();

// Shape s1 = (Shape)c;
Shape s1 = c // 不須 Cast

s1.Area();

}

public class Shape
{
public virtual void Draw()
{
Console.WriteLine("Base Draw");
}
}

public class Circle : Shape
{
// public override void Draw()
// {
// Console.WriteLine("Derived Class Draw");
// }
}

如果把 Draw 進行覆寫 (也就是把註解拿掉),他就會執行覆寫後的方法,反之他會執行基底類別的 Draw 方法

結論

經由多型這個特性,我們在撰寫程式的時候就可以變得比較有彈性一點,如果在衍生類別還想加入一些其他的邏輯,那麼基底類別可以標記為 virtual 關鍵字,保留讓衍生類別改寫(或部分改寫)基底類別的彈性。