在上篇文章 C# 繼承與多型 1 : 量測記憶體使用情況 中,我們使用記憶體耗用的角度,來查看當進行類別繼承設計的時候,基底類別與延伸類別所產生的物件,到底會耗用多少的記憶體,現在,在這篇文章中,我們將會來觀察當類別進行繼承的時候,對於多型行為的運作方式,會有幾種,以及會出現甚麼樣的變化。
這篇文章將會建立一個名為 PolymorphismBehavior 的 .NET Core 主控制台應用程式專案
了解更多關於 [多型]
了解更多關於 [C# 程式設計手冊]
首先,我們會先來檢測單存的繼承使用方式,我們宣告基底類別 MyBaseClase,他只有兩個屬性,一個是 Name,一個屬性是 Address ,另外,宣告一個衍生類別 MyDerivedClass,他繼承了 基底類別 MyBaseClase,我們在衍生類別中。
我們在基底類別之建構式內,進行這兩屬性值的初始化,不過,我們在衍生類別的建構式內,僅有進行 Address 這個屬性值的初始化。
namespace ByInheritance
{
public class MyBaseClase
{
public string Name { get; set; }
public string Address { get; set; }
public MyBaseClase()
{
Name = "Base Class Name";
Address = "Base Class Address";
}
}
public class MyDerivedClass : MyBaseClase
{
public MyDerivedClass()
{
//Name = "Derived Class Name";
Address = "Derived Class Address";
}
}
}
現在,我們使用底下的程式碼來進行測試繼承與多型的使用行為。
首先,我們僅建立一個 衍生類別 的物件,並且將這個物件轉型為基底型別,並且指定給另外一個本地變數。
經過呼叫這兩個不同型別,但是指向同一個實體物件的本地變數之 GetType().Name ,我們得到執行時期的這兩個本地變數,都是指向為一個衍生類別建構式所建立出來的物件。
#region 單純繼承之物件多型行為
ByInheritance.MyDerivedClass fooMyDerivedClassObject = new ByInheritance.MyDerivedClass();
ByInheritance.MyBaseClase fooMyBaseClassObject = fooMyDerivedClassObject as ByInheritance.MyBaseClase;
Console.WriteLine("單純繼承之物件多型行為");
Console.WriteLine($"型別為基底類別的本地變數之執行時期的型別為 : {fooMyBaseClassObject.GetType().Name} ");
Console.WriteLine($"型別為衍生類別的本地變數之執行時期的型別為 : {fooMyDerivedClassObject.GetType().Name} ");
Console.WriteLine($"型別為基底類別的本地變數之 Name 屬性值為 : {fooMyBaseClassObject.Name} ");
Console.WriteLine($"型別為基底類別的本地變數之 Address 屬性值為 : {fooMyBaseClassObject.Address} ");
Console.WriteLine($"型別為衍生類別的本地變數之 Name 屬性值為 : {fooMyDerivedClassObject.Name} ");
Console.WriteLine($"型別為衍生類別的本地變數之 Address 屬性值為 : {fooMyDerivedClassObject.Address} ");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
#endregion
接著,我們分別顯示出 基底類別型別的本地變數內的 Name / Address 屬性值與 基底類別型別的本地變數內的 Name / Address 屬性值。
因為我們這裡僅使用單純的資料繼承,因此,不論您當時的物件,是由哪個衍生類別建構式所生成得,這個物件內的屬性值,僅會有一份,那就是定義在基底類別中的兩個屬性;而因為我們在基底類別的建構是有進行這 Name 的屬性值初始化的動作,因此,這兩個本地變數中的 Name 屬性值,都是相同的;而又因為我們在衍生類別的建構式有做 Address 的屬性值初始化設定,不過,因為 Address 在這個例子中的任何一個本地變數內,都僅會有一份,因此, Address 的屬性值將會受到衍生類別建構式執行的影響而有所改變 (你可以說得出理由嗎?)
單純繼承之物件多型行為
型別為基底類別的本地變數之執行時期的型別為 : MyDerivedClass
型別為衍生類別的本地變數之執行時期的型別為 : MyDerivedClass
型別為基底類別的本地變數之 Name 屬性值為 : Base Class Name
型別為基底類別的本地變數之 Address 屬性值為 : Derived Class Address
型別為衍生類別的本地變數之 Name 屬性值為 : Base Class Name
型別為衍生類別的本地變數之 Address 屬性值為 : Derived Class Address
現在,我們會先來檢測繼承中有使用 virtual 與 overide 方式,我們宣告基底類別 MyBaseClase,他只有兩個屬性,一個是 Name,一個屬性是 Address 這兩個屬性都有標示 virtual;另外,宣告一個衍生類別 MyDerivedClass,他繼承了 基底類別 MyBaseClase,我們在衍生類別中另外使用 override 來宣告了相同的屬性。
我們在基底類別之建構式內,進行這兩屬性值的初始化,不過,我們在衍生類別的建構式內,也分別進行這兩個屬性值的初始化。不過,我們在基底類別建構函式與衍生類別建構式內,對於相同名稱的屬性,設定了不同的內容。
namespace ByInheritancePolymorphismVirtualOverride
{
public class MyBaseClase
{
public virtual string Name { get; set; }
public virtual string Address { get; set; }
public MyBaseClase()
{
Name = "Base Class Name";
Address = "Base Class Address";
}
}
public class MyDerivedClass : MyBaseClase
{
public override string Name { get; set; }
public override string Address { get; set; }
public MyDerivedClass()
{
Name = "Derived Class Name";
Address = "Derived Class Address";
}
}
}
現在,我們使用底下的程式碼來進行測試繼承與多型的使用行為。
首先,我們僅建立一個 衍生類別 的物件,並且將這個物件轉型為基底型別,並且指定給另外一個本地變數。
經過呼叫這兩個不同型別,但是指向同一個實體物件的本地變數之 GetType().Name ,我們得到執行時期的這兩個本地變數,都是指向為一個衍生類別建構式所建立出來的物件。
#region 繼承與多型覆寫之有使用 virtual / override 使用情況
ByInheritancePolymorphismVirtualOverride.MyDerivedClass fooVirtualOverrideMyDerivedClassObject = new ByInheritancePolymorphismVirtualOverride.MyDerivedClass();
ByInheritancePolymorphismVirtualOverride.MyBaseClase fooVirtualOverrideMyBaseClassObject = fooVirtualOverrideMyDerivedClassObject as ByInheritancePolymorphismVirtualOverride.MyBaseClase;
Console.WriteLine("繼承與多型覆寫之有使用 virtual / override 使用情況");
Console.WriteLine($"型別為基底類別的本地變數之執行時期的型別為 : {fooVirtualOverrideMyBaseClassObject.GetType().Name} ");
Console.WriteLine($"型別為衍生類別的本地變數之執行時期的型別為 : {fooVirtualOverrideMyDerivedClassObject.GetType().Name} ");
Console.WriteLine($"型別為基底類別的本地變數之 Name 屬性值為 : {fooVirtualOverrideMyBaseClassObject.Name} ");
Console.WriteLine($"型別為基底類別的本地變數之 Address 屬性值為 : {fooVirtualOverrideMyBaseClassObject.Address} ");
Console.WriteLine($"型別為衍生類別的本地變數之 Name 屬性值為 : {fooVirtualOverrideMyDerivedClassObject.Name} ");
Console.WriteLine($"型別為衍生類別的本地變數之 Address 屬性值為 : {fooVirtualOverrideMyDerivedClassObject.Address} ");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
#endregion
在底下的執行結果,我們看到了,不論型別為基底類別或者是衍生類別的區域變數,若在宣告類別的時候,在基底類別有使用到 virtual ,而在衍生類別中有使用到 override,此時,當要存取區域變數內的物件屬性值的時候,就會依照該物件的最初祖先,逐一往下衍生類別進行探詢,在這個範例中,因為當時的物件指向的 衍生類別 所產生出來的物件,因此,我們都會顯示出 衍生類別 內的屬性值。
繼承與多型覆寫之有使用 virtual / override 使用情況
型別為基底類別的本地變數之執行時期的型別為 : MyDerivedClass
型別為衍生類別的本地變數之執行時期的型別為 : MyDerivedClass
型別為基底類別的本地變數之 Name 屬性值為 : Derived Class Name
型別為基底類別的本地變數之 Address 屬性值為 : Derived Class Address
型別為衍生類別的本地變數之 Name 屬性值為 : Derived Class Name
型別為衍生類別的本地變數之 Address 屬性值為 : Derived Class Address
現在,讓我們來看看,若我們在基底類別中有使用 virtual,但是,在衍生類別中,不去使用 override,而是使用了 new (隱藏),會發生甚麼問題?
namespace ByInheritancePolymorphismNew
{
public class MyBaseClase
{
public virtual string Name { get; set; }
public virtual string Address { get; set; }
public MyBaseClase()
{
Name = "Base Class Name";
Address = "Base Class Address";
}
}
public class MyDerivedClass : MyBaseClase
{
public new string Name { get; set; }
public new string Address { get; set; }
public MyDerivedClass()
{
Name = "Derived Class Name";
Address = "Derived Class Address";
}
}
}
這裡是與上面類似的執行測試程式碼
#region 繼承與多型覆寫之有使用 new 使用情況
ByInheritancePolymorphismNew.MyDerivedClass fooNewMyDerivedClassObject = new ByInheritancePolymorphismNew.MyDerivedClass();
ByInheritancePolymorphismNew.MyBaseClase fooNewMyBaseClassObject = fooNewMyDerivedClassObject as ByInheritancePolymorphismNew.MyBaseClase;
Console.WriteLine("繼承與多型覆寫之有使用 new 使用情況");
Console.WriteLine($"型別為基底類別的本地變數之執行時期的型別為 : {fooNewMyBaseClassObject.GetType().Name} ");
Console.WriteLine($"型別為衍生類別的本地變數之執行時期的型別為 : {fooNewMyDerivedClassObject.GetType().Name} ");
Console.WriteLine($"型別為基底類別的本地變數之 Name 屬性值為 : {fooNewMyBaseClassObject.Name} ");
Console.WriteLine($"型別為基底類別的本地變數之 Address 屬性值為 : {fooNewMyBaseClassObject.Address} ");
Console.WriteLine($"型別為衍生類別的本地變數之 Name 屬性值為 : {fooNewMyDerivedClassObject.Name} ");
Console.WriteLine($"型別為衍生類別的本地變數之 Address 屬性值為 : {fooNewMyDerivedClassObject.Address} ");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
#endregion
從執行結果來看,因為我們在衍生類別的屬性上,宣告了 new ,這代表要隱藏基底類別的屬性資訊,而不會產生執行時期的參與虛擬引動過程。所以,型別為基底型別的區域變數,就會存取到基底型別的屬性值,而型別為衍生類別的區域變數,會存取到衍生類別的屬性值。
繼承與多型覆寫之有使用 new 使用情況
型別為基底類別的本地變數之執行時期的型別為 : MyDerivedClass
型別為衍生類別的本地變數之執行時期的型別為 : MyDerivedClass
型別為基底類別的本地變數之 Name 屬性值為 : Base Class Name
型別為基底類別的本地變數之 Address 屬性值為 : Base Class Address
型別為衍生類別的本地變數之 Name 屬性值為 : Derived Class Name
型別為衍生類別的本地變數之 Address 屬性值為 : Derived Class Address
現在,讓我們來做個綜合測試,我們
namespace ByInheritancePolymorphismMixVirtualAndNew
{
public class MyBaseClase
{
public virtual string Name { get; set; }
public virtual string Address { get; set; }
public int Age { get; set; }
public MyBaseClase()
{
Name = "Base Class Name";
Address = "Base Class Address";
Age = 20;
}
}
public class MyDerivedClass : MyBaseClase
{
public override string Name { get; set; }
public new string Address { get; set; }
public new int Age { get; set; }
public MyDerivedClass()
{
Name = "Derived Class Name";
Address = "Derived Class Address";
Age = 50;
}
}
}
這是我們要執行的測試程式碼,我們在這裡加入了分別顯示基底型別變數與衍生型別變數的 Age 屬性
#region 繼承與多型覆寫之有使用 virtual / override / new 使用情況
ByInheritancePolymorphismMixVirtualAndNew.MyDerivedClass fooMixVirtualAndNewMyDerivedClassObject = new ByInheritancePolymorphismMixVirtualAndNew.MyDerivedClass();
ByInheritancePolymorphismMixVirtualAndNew.MyBaseClase fooMixVirtualAndNewMyBaseClassObject = fooMixVirtualAndNewMyDerivedClassObject as ByInheritancePolymorphismMixVirtualAndNew.MyBaseClase;
Console.WriteLine("承與多型覆寫之有使用 virtual / override / new 使用情況");
Console.WriteLine($"型別為基底類別的本地變數之執行時期的型別為 : {fooMixVirtualAndNewMyBaseClassObject.GetType().Name} ");
Console.WriteLine($"型別為衍生類別的本地變數之執行時期的型別為 : {fooMixVirtualAndNewMyDerivedClassObject.GetType().Name} ");
Console.WriteLine($"型別為基底類別的本地變數之 Name 屬性值為 : {fooMixVirtualAndNewMyBaseClassObject.Name} ");
Console.WriteLine($"型別為基底類別的本地變數之 Address 屬性值為 : {fooMixVirtualAndNewMyBaseClassObject.Address} ");
Console.WriteLine($"型別為基底類別的本地變數之 Age 屬性值為 : {fooMixVirtualAndNewMyBaseClassObject.Age} ");
Console.WriteLine($"型別為衍生類別的本地變數之 Name 屬性值為 : {fooMixVirtualAndNewMyDerivedClassObject.Name} ");
Console.WriteLine($"型別為衍生類別的本地變數之 Address 屬性值為 : {fooMixVirtualAndNewMyDerivedClassObject.Address} ");
Console.WriteLine($"型別為衍生類別的本地變數之 Age 屬性值為 : {fooMixVirtualAndNewMyDerivedClassObject.Age} ");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
#endregion
這是最終執行結果,根據前面三個的說明,您能夠理解為什麼會出現這樣的結果嗎?
承與多型覆寫之有使用 virtual / override / new 使用情況
型別為基底類別的本地變數之執行時期的型別為 : MyDerivedClass
型別為衍生類別的本地變數之執行時期的型別為 : MyDerivedClass
型別為基底類別的本地變數之 Name 屬性值為 : Derived Class Name
型別為基底類別的本地變數之 Address 屬性值為 : Base Class Address
型別為基底類別的本地變數之 Age 屬性值為 : 20
型別為衍生類別的本地變數之 Name 屬性值為 : Derived Class Name
型別為衍生類別的本地變數之 Address 屬性值為 : Derived Class Address
型別為衍生類別的本地變數之 Age 屬性值為 : 50
關於 Xamarin 在台灣的學習技術資源
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程