2018年7月7日 星期六

C# 量測繼承與多型 2 : 覆寫與隱藏之屬性運作行為研究

在上篇文章 C# 繼承與多型 1 : 量測記憶體使用情況 中,我們使用記憶體耗用的角度,來查看當進行類別繼承設計的時候,基底類別與延伸類別所產生的物件,到底會耗用多少的記憶體,現在,在這篇文章中,我們將會來觀察當類別進行繼承的時候,對於多型行為的運作方式,會有幾種,以及會出現甚麼樣的變化。

了解更多關於 [多型
了解更多關於 [C# 程式設計手冊 






這篇文章將會建立一個名為 PolymorphismBehavior 的 .NET Core 主控制台應用程式專案
首先,我們會先來檢測單存的繼承使用方式,我們宣告基底類別 MyBaseClase,他只有兩個屬性,一個是 Name,一個屬性是 Address ,另外,宣告一個衍生類別 MyDerivedClass,他繼承了 基底類別 MyBaseClase,我們在衍生類別中。
我們在基底類別之建構式內,進行這兩屬性值的初始化,不過,我們在衍生類別的建構式內,僅有進行 Address 這個屬性值的初始化。
C Sharp / C#
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 ,我們得到執行時期的這兩個本地變數,都是指向為一個衍生類別建構式所建立出來的物件。
C Sharp / C#
#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 的屬性值將會受到衍生類別建構式執行的影響而有所改變 (你可以說得出理由嗎?)
Console
單純繼承之物件多型行為
型別為基底類別的本地變數之執行時期的型別為 : 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 來宣告了相同的屬性。
我們在基底類別之建構式內,進行這兩屬性值的初始化,不過,我們在衍生類別的建構式內,也分別進行這兩個屬性值的初始化。不過,我們在基底類別建構函式與衍生類別建構式內,對於相同名稱的屬性,設定了不同的內容。
C Sharp / C#
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 ,我們得到執行時期的這兩個本地變數,都是指向為一個衍生類別建構式所建立出來的物件。
C Sharp / C#
#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,此時,當要存取區域變數內的物件屬性值的時候,就會依照該物件的最初祖先,逐一往下衍生類別進行探詢,在這個範例中,因為當時的物件指向的 衍生類別 所產生出來的物件,因此,我們都會顯示出 衍生類別 內的屬性值。
Console
繼承與多型覆寫之有使用 virtual / override 使用情況
型別為基底類別的本地變數之執行時期的型別為 : MyDerivedClass
型別為衍生類別的本地變數之執行時期的型別為 : MyDerivedClass
型別為基底類別的本地變數之 Name 屬性值為 : Derived Class Name
型別為基底類別的本地變數之 Address 屬性值為 : Derived Class Address
型別為衍生類別的本地變數之 Name 屬性值為 : Derived Class Name
型別為衍生類別的本地變數之 Address 屬性值為 : Derived Class Address
現在,讓我們來看看,若我們在基底類別中有使用 virtual,但是,在衍生類別中,不去使用 override,而是使用了 new (隱藏),會發生甚麼問題?
C Sharp / C#
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";
        }
    }

}
這裡是與上面類似的執行測試程式碼
C Sharp / C#
#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 ,這代表要隱藏基底類別的屬性資訊,而不會產生執行時期的參與虛擬引動過程。所以,型別為基底型別的區域變數,就會存取到基底型別的屬性值,而型別為衍生類別的區域變數,會存取到衍生類別的屬性值。
Console
繼承與多型覆寫之有使用 new 使用情況
型別為基底類別的本地變數之執行時期的型別為 : MyDerivedClass
型別為衍生類別的本地變數之執行時期的型別為 : MyDerivedClass
型別為基底類別的本地變數之 Name 屬性值為 : Base Class Name
型別為基底類別的本地變數之 Address 屬性值為 : Base Class Address
型別為衍生類別的本地變數之 Name 屬性值為 : Derived Class Name
型別為衍生類別的本地變數之 Address 屬性值為 : Derived Class Address
現在,讓我們來做個綜合測試,我們
C Sharp / C#
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 屬性
C Sharp / C#
#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
這是最終執行結果,根據前面三個的說明,您能夠理解為什麼會出現這樣的結果嗎?
Console
承與多型覆寫之有使用 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


了解更多關於 [多型
了解更多關於 [C# 程式設計手冊 







關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程

2018年7月6日 星期五

C# 繼承與多型 1 : 量測記憶體使用情況

在我們這篇文章將會以記憶體耗用的角度,來查看當進行類別繼承設計的時候,基底類別與延伸類別所產生的物件,到底會耗用多少的記憶體來進行觀察。
這篇文章將會建立一個名為 MeasureObjectSize 的 .NET Core 主控制台應用程式專案
首先,我們會宣告三個類別,一個是空的類別 MyEmptyClass,在這裡面甚麼都沒有宣告與定義,第二個是基底類別 MyBaseClase,他只有一個 Number 屬性並且為一個整數陣列,長度為 1024 個,第三個為衍生類別 MyDerivedClass,他繼承了 基底類別 MyBaseClase
C Sharp / C#
namespace ByInheritance
{
    public class MyBaseClase
    {
        protected int[] Number { get; set; } = new int[1024];
    }

    public class MyEmptyClass
    {

    }
    public class MyDerivedClass : MyBaseClase
    {

    }
}
我們開始進行建立與量測要建立不同類別物件的時候,究竟會耗用多少記憶體空間。由於在 .NET 環境之下,並沒有提供任何 API 可以讓您量測一個物件耗用了多少記憶體空間,因此,我們使用了 GC.GetTotalMemory 方法.aspx) 幫助我們取得 目前配置於 managed 記憶體中的位元組數目。
首先,我們先來量測,當建立了一個 1024 長度的整數陣列,會耗用多少的記憶體空間,所以,我們先取得現在執行環境中配置於 managed 記憶體中的位元組數目,接著,建立一個 1024 長度的整數陣列,然後再度取得配置於 managed 記憶體中的位元組數目,最後,兩者相減,就可以得到 1024 長度的整數陣列在 .NET 環境中,使用了多少記憶體空間。
我們得到了 4120 位元組這個數值
C Sharp / C#
long BeginMemoryUsage, ArrayMemoryUsage, 
    EmptyClassObjectMemoryUsage, BaseClassObjectMemoryUsage, DerivedClassObjectMemoryUsage;
int[] localNumber;

BeginMemoryUsage = GC.GetTotalMemory(true);
localNumber = new int[1024];
ArrayMemoryUsage = GC.GetTotalMemory(true);
Console.WriteLine("量測 int[1024] 的耗用記憶體");
Console.WriteLine($"int[1024] 耗用記憶體 : {ArrayMemoryUsage - BeginMemoryUsage} ");

Console.WriteLine("Press any key for continuing...");
Console.ReadKey();

BeginMemoryUsage = GC.GetTotalMemory(true);
ByInheritance.MyEmptyClass fooMyEmptyClass = new ByInheritance.MyEmptyClass();
EmptyClassObjectMemoryUsage = GC.GetTotalMemory(true);
ByInheritance.MyBaseClase fooMyBaseClassObject = new ByInheritance.MyBaseClase();
BaseClassObjectMemoryUsage = GC.GetTotalMemory(true);
ByInheritance.MyDerivedClass fooMyDerivedClassObject = new ByInheritance.MyDerivedClass();
DerivedClassObjectMemoryUsage = GC.GetTotalMemory(true);

Console.WriteLine("量測單純繼承之物件使用記憶體使用情況");
Console.WriteLine($"產生空類別  之執行個體耗用記憶體 : {EmptyClassObjectMemoryUsage - BeginMemoryUsage} ");
Console.WriteLine($"建立基底類別之執行個體耗用記憶體 : {BaseClassObjectMemoryUsage - EmptyClassObjectMemoryUsage} ");
Console.WriteLine($"建立衍生類別之執行個體耗用記憶體 : {DerivedClassObjectMemoryUsage - BaseClassObjectMemoryUsage} ");
Console.WriteLine($"衍生類別之執行個體 與 基底類別之執行個體 記憶體相差數量 : " +
    $"{(BaseClassObjectMemoryUsage - EmptyClassObjectMemoryUsage) - (DerivedClassObjectMemoryUsage - BaseClassObjectMemoryUsage)} ");

Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
現在,我們使用相同的手法,分別來產生 空類別 Empty 、 基底類別 Base Class 、 衍生類別 Derived Class 的物件,並且得到他們分別耗用了多少記憶體空間,底下是我們的執行結果。
我們看到了,基底類別與衍生類別所產生的物件,他們具有相同的記憶體使用空間,你知道為什麼嗎?
Console
量測單純繼承之物件使用記憶體使用情況
產生空類別  之執行個體耗用記憶體 : 24
建立基底類別之執行個體耗用記憶體 : 4144
建立衍生類別之執行個體耗用記憶體 : 4144
衍生類別之執行個體 與 基底類別之執行個體 記憶體相差數量 : 0
Press any key for continuing...
剛剛我們看到了衍生類別繼承了基底類別,不過,衍生類別裡面沒有另外新增任何類別成員,他們耗用相同的記憶體空間。現在,我們在基底類別的陣列屬性成員前,加入 virtual 關鍵字,並且在衍生類別內,使用 override 來覆寫這個屬性,那麼,對於由基底類別與衍生類別所產生出來的物件,其佔用記憶體情況又會是如何呢?
C Sharp / C#
namespace ByInheritancePolymorphismOverride
{
    public class MyBaseClase
    {
        protected virtual int[] Number { get; set; } = new int[1024];
    }
    public class MyEmptyClass
    {

    }
    public class MyDerivedClass : MyBaseClase
    {
        protected override int[] Number { get; set; } = new int[1024];
    }
}
現在,就讓我們也使用相同的技巧,分別建立起基底類別與衍生類別的物件,計算出這兩個物件到底佔用了多少記憶體空間。
C Sharp / C#
long BeginMemoryUsage, ArrayMemoryUsage, 
    EmptyClassObjectMemoryUsage, BaseClassObjectMemoryUsage, DerivedClassObjectMemoryUsage;
int[] localNumber;

Console.WriteLine("量測單純繼承之物件使用記憶體使用情況");
Console.WriteLine($"產生空類別  之執行個體耗用記憶體 : {EmptyClassObjectMemoryUsage - BeginMemoryUsage} ");
Console.WriteLine($"建立基底類別之執行個體耗用記憶體 : {BaseClassObjectMemoryUsage - EmptyClassObjectMemoryUsage} ");
Console.WriteLine($"建立衍生類別之執行個體耗用記憶體 : {DerivedClassObjectMemoryUsage - BaseClassObjectMemoryUsage} ");
Console.WriteLine($"衍生類別之執行個體 與 基底類別之執行個體 記憶體相差數量 : " +
    $"{(BaseClassObjectMemoryUsage - EmptyClassObjectMemoryUsage) - (DerivedClassObjectMemoryUsage - BaseClassObjectMemoryUsage)} ");

Console.WriteLine("Press any key for continuing...");
Console.ReadKey();

BeginMemoryUsage = GC.GetTotalMemory(true);
ByInheritancePolymorphismOverride.MyEmptyClass fooInheritancePolymorphismMyEmptyClass = new ByInheritancePolymorphismOverride.MyEmptyClass();
EmptyClassObjectMemoryUsage = GC.GetTotalMemory(true);
ByInheritancePolymorphismOverride.MyBaseClase fooInheritancePolymorphismMyBaseClassObject = new ByInheritancePolymorphismOverride.MyBaseClase();
BaseClassObjectMemoryUsage = GC.GetTotalMemory(true);
ByInheritancePolymorphismOverride.MyDerivedClass fooInheritancePolymorphismMyDerivedClassObject = new ByInheritancePolymorphismOverride.MyDerivedClass();
DerivedClassObjectMemoryUsage = GC.GetTotalMemory(true);

Console.WriteLine("量測繼承與多型覆寫之物件使用記憶體使用情況");
Console.WriteLine($"產生空類別  之執行個體耗用記憶體 : {EmptyClassObjectMemoryUsage - BeginMemoryUsage} ");
Console.WriteLine($"建立基底類別之執行個體耗用記憶體 : {BaseClassObjectMemoryUsage - EmptyClassObjectMemoryUsage} ");
Console.WriteLine($"建立衍生類別之執行個體耗用記憶體 : {DerivedClassObjectMemoryUsage - BaseClassObjectMemoryUsage} ");
Console.WriteLine($"衍生類別之執行個體 與 基底類別之執行個體 記憶體相差數量 : " +
    $"{(DerivedClassObjectMemoryUsage - BaseClassObjectMemoryUsage)- (BaseClassObjectMemoryUsage - EmptyClassObjectMemoryUsage)} ");

Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
這是執行結果內容,我們看到了,竟然衍生類別所建立起來的物件所耗用的記憶體大小,是基底類別的一倍,那這到底發生了甚麼問題?這是因為衍生類別繼承的基底類別,因此,每個基底類別的成員所耗用的記憶體空間,都會保留在衍生類別所建立的物件,也就是多了一份,而衍生類別因為使用了繼承多型的覆寫,所以,在衍生類別所產生的物件中,也會有一份 1024 長度的陣列整數記憶體空間,您能夠理解嗎?
Console
量測繼承與多型覆寫之物件使用記憶體使用情況
產生空類別  之執行個體耗用記憶體 : 24
建立基底類別之執行個體耗用記憶體 : 4144
建立衍生類別之執行個體耗用記憶體 : 8272
衍生類別之執行個體 與 基底類別之執行個體 記憶體相差數量 : 4128
最後,我們在基底類別的屬性成員前面將不會加入任何的 virtual 關鍵字,並且在衍生類別屬性成員中宣告了 new 這個隱藏基底成員
C Sharp / C#
namespace ByInheritancePolymorphismNew
{
    public class MyBaseClase
    {
        protected int[] Number { get; set; } = new int[1024];
    }
    public class MyEmptyClass
    {

    }
    public class MyDerivedClass : MyBaseClase
    {
        protected new int[] number { get; set; } = new int[1024];
    }
}
我們也使用了相同的記憶體使用量的量測技術,分別檢視基底類別與衍生類別他們所耗用的記憶體大小
C Sharp / C#
long BeginMemoryUsage, ArrayMemoryUsage, 
    EmptyClassObjectMemoryUsage, BaseClassObjectMemoryUsage, DerivedClassObjectMemoryUsage;
int[] localNumber;

BeginMemoryUsage = GC.GetTotalMemory(true);
localNumber = new int[1024];
ArrayMemoryUsage = GC.GetTotalMemory(true);
Console.WriteLine("量測 int[1024] 的耗用記憶體");
Console.WriteLine($"int[1024] 耗用記憶體 : {ArrayMemoryUsage - BeginMemoryUsage} ");

Console.WriteLine("Press any key for continuing...");
Console.ReadKey();

BeginMemoryUsage = GC.GetTotalMemory(true);
ByInheritancePolymorphismNew.MyEmptyClass fooInheritancePolymorphismNewMyEmptyClass = new ByInheritancePolymorphismNew.MyEmptyClass();
EmptyClassObjectMemoryUsage = GC.GetTotalMemory(true);
ByInheritancePolymorphismNew.MyBaseClase fooInheritancePolymorphismNewMyBaseClassObject = new ByInheritancePolymorphismNew.MyBaseClase();
BaseClassObjectMemoryUsage = GC.GetTotalMemory(true);
ByInheritancePolymorphismNew.MyDerivedClass fooInheritancePolymorphismNewMyDerivedClassObject = new ByInheritancePolymorphismNew.MyDerivedClass();
DerivedClassObjectMemoryUsage = GC.GetTotalMemory(true);

Console.WriteLine("量測繼承與多型隱藏之物件使用記憶體使用情況");
Console.WriteLine($"產生空類別  之執行個體耗用記憶體 : {EmptyClassObjectMemoryUsage - BeginMemoryUsage} ");
Console.WriteLine($"建立基底類別之執行個體耗用記憶體 : {BaseClassObjectMemoryUsage - EmptyClassObjectMemoryUsage} ");
Console.WriteLine($"建立衍生類別之執行個體耗用記憶體 : {DerivedClassObjectMemoryUsage - BaseClassObjectMemoryUsage} ");
Console.WriteLine($"衍生類別之執行個體 與 基底類別之執行個體 記憶體相差數量 : " +
    $"{(DerivedClassObjectMemoryUsage - BaseClassObjectMemoryUsage)- (BaseClassObjectMemoryUsage - EmptyClassObjectMemoryUsage)} ");

Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
這是執行結果內容,我們看到了,竟然衍生類別所建立起來的物件所耗用的記憶體大小,是基底類別的一倍,那這到底發生了甚麼問題?這是因為衍生類別繼承的基底類別,因此,每個基底類別的成員所耗用的記憶體空間,都會保留在衍生類別所建立的物件,也就是多了一份,而衍生類別因為使用了繼承多型的隱藏,所以,在衍生類別所產生的物件中,也會有一份 1024 長度的陣列整數記憶體空間。
所以,只要在繼承關係的時候,有在衍生類別中,重新定義屬性成員,不論您使用了 override 覆寫或者是使用了 new 隱藏關鍵字,這個時候,當衍生類別物件產生了時候,除了衍生類別中宣告的成員會占用記憶體之外,衍生類別所產生的物件,也會包含了基底類別的屬性成員所需要用到的記憶體大小。
Console
量測繼承與多型隱藏之物件使用記憶體使用情況
產生空類別  之執行個體耗用記憶體 : 24
建立基底類別之執行個體耗用記憶體 : 4144
建立衍生類別之執行個體耗用記憶體 : 8272
衍生類別之執行個體 與 基底類別之執行個體 記憶體相差數量 : 4128

關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程

2018年7月5日 星期四

C# Switch 重構 Refactoring 2 : 使用資料字典與多型或委派方法或 Lambda

在上一篇文章中, 
C# Switch 重構 Refactoring 1 : 使用資料字典
,我們嘗試要將複雜的 Switch 敘述,透過資料字典的方式,重構這個 Switch 需要做到的功能,我們可以看得出來,當我們透過了 資料字典 Data Dictionary 的方式重構之後,因為透過了資料字典,可以快速的找到需要進行顏色轉換的方法 (為什麼會比較快呢?);現在,當我們在每個 case 中需要處理的工作,不是短短的一行程式碼就可以解決的,我們該如何處理這樣的問題呢?在這裡,我們將嘗試透過重構的方法,搭配使用資料字典與委派方法或者 Lambda 匿名委派方法,將可以解決此一問題。
在這裡,我會建立 RefactorSwitchDelegate 專案來進行說明 (這裡的練習專案,將會建立一個 .NET Core 主控制台應用程式專案)
首先,我們原來使用 Switch 關鍵字的程式碼如下,我們有個類別 StringToColor,裡面僅有一個方法 Transfer,在此方法內,使用 Switch 來判斷傳入的字串顏色名稱,產生出該顏色的 Color 類別物件。
C Sharp / C#
public class StringToColor
{
    public Color Transfer(string name)
    {
        Color transferResult;

        switch (name.ToLower())
        {
            case "red":
                transferResult = Color.FromArgb(0xFF, 0xFF, 0x00, 0x00);
                break;
            case "green":
                transferResult = Color.FromArgb(0xFF, 0x80, 0x80, 0x80);
                break;
            case "blue":
                transferResult = Color.FromArgb(0xFF, 0x00, 0x00, 0xFF);
                break;
            default:
                throw new ArgumentException("不正確的顏色名稱");
        }
        return transferResult;
    }
}
在這裡,我們來看看如何使用這個支援類別的將字串轉換成為 Color 類別物件。
首先,我們建立一個 StringToColor 類別的物件,緊接著呼叫 Transfer 方法,我們在這個方法傳入一個字串 (Red) 進去,當然,我們將會取得一個 Color 物件,並且把這個物件的顏色屬性顯示在螢幕上。
C Sharp / C#
class Program
{
    static void Main(string[] args)
    { 
        string MyColorName = "Blue";

        Console.WriteLine("使用 Swith 來設計方法");

        StringToColor foo = new StringToColor();
        Color fooColor = foo.Transfer(MyColorName);

        Console.WriteLine($"{MyColorName} =  A:{fooColor.A} R:{fooColor.R} G:{fooColor.G} B:{fooColor.B}");
        Console.WriteLine("Press any key for continuing...");
    }
}
這裡是執行結果
Console
使用 Swith 來設計方法
Blue =  A:255 R:0 G:0 B:255
Press any key for continuing...
現在,我們要將顏色轉換的敘述,分別產生一個類別,將這些敘述轉移到那哩,例如,想要取得紅色顏,建立一個 TransferToRed 類別執行個體,透過該執行個體中的 MakeTransfer 方法,就可以取得 紅色的 Color 物件。
不過,當我們需要依據各種不同顏色文字,自己判斷要呼叫哪個類別的執行個體,這樣的話,似乎又回到了 if ... else if ... / switch 的作法,因此,在這裡,我們將要透過繼承的方式來進行實作,所以,在這裡,我們建立了一個抽象類別 ColorTransfer,這個抽象類別中,僅有一個抽象方法 Transfer,所以,在我們的用戶端中,我們只需要面對 ColorTransfer 這個型別的變數,便可以套用多型的規則,自動產生出相對應的顏色物件。
經過這樣的重構,下次當我們有新的顏色文字要對應產生 Color 物件的時候,我們僅需要建立一個 TransferToNewColor 這樣的類別,並且要繼承抽象類別 ColorTransfer與實作該抽象類別中的抽象方法即可,如此,便又有新的顏色轉換規則可以使用了。
C Sharp / C#
namespace RefactorSwitchDictionary.Refactoring
{
    #region 將不同顏色名稱轉換成為顏色物件的過程,設計成為不同的類別
    public abstract class ColorTransfer
    {
        public abstract Color Transfer();
    }
    public class TransferToRed : ColorTransfer
    {
        public override Color Transfer()
        {
            return Color.FromArgb(0xFF, 0xFF, 0x00, 0x00);
        }
    }
    public class TransferToGreen : ColorTransfer
    {
        public override Color Transfer()
        {
            return Color.FromArgb(0xFF, 0x80, 0x80, 0x80);
        }
    }
    public class TransferToBlue : ColorTransfer
    {
        public override Color Transfer()
        {
            return Color.FromArgb(0xFF, 0x00, 0x00, 0xFF);
        }
    }
    #endregion
}
好的,讓我們回到我們最初的類別 StringToColor,我們要如何套用剛剛寫好的抽象類別與顏色轉換具體實作類別到這個 StringToColor 類別內呢?在這裡,我們先使用相同 資料字典 的模式,不過,在這裡,我們定義的資料字典為 Dictionary ,鍵值 Key 一樣是 字串,也就是要轉換的顏色文字敘述,而 值 Value 部分,我們將會變更成為各個顏色轉換的類別而產生的物件,在這裡,我們使用了 ColorTransfer 這個抽象類別,因為,所有的顏色轉換實作類別,都會繼承與實作這個抽象類別,透過多型的特色,我們可以依據當時真正物件的型別,來呼叫 Transfer 方法,進行顏色物件的產生。
C Sharp / C#
namespace RefactorSwitchDictionary.Refactoring
{
    public class StringToColor
    {
        Dictionary<string, ColorTransfer> _colorMaps;
        public StringToColor()
        {
            _colorMaps = new Dictionary<string, ColorTransfer>()
            {
                {"Red", new TransferToRed() },
                {"Green", new TransferToGreen() },
                {"Blue", new TransferToBlue() },
            };
        }
        public Color Transfer(string name)
        {
            if (!_colorMaps.ContainsKey(name))
                throw new ArgumentException("不正確的顏色名稱");
            return _colorMaps[name].Transfer();
        }
    }
    #endregion
}
我們來看看重構之後對於 StringToColor 這個類別的用法,有沒有甚麼不同;由於我們將重構完成的 StringToColor 類別設計在 RefactorSwitchDictionary.Refactoring 命名空間之內,因此,在這裡,我們引用 StringToColor 類別的時候,將會加入 RefactorSwitchDictionary.Refactoring 命名空間到這個類別前。
您將會發現到,我們重構後的類別用法,與重構前的用法沒有不同,不過,是不是程式碼變成更加精簡與容易閱讀和維護了呢?
C Sharp / C#
class Program
{
    static void Main(string[] args)
    { 
        string MyColorName = "Blue";

            Console.WriteLine("使用 資料字典與多型 來重構方法,進行 Swith 需求設計");

            RefactorSwitchDictionary.Refactoring.StringToColor fooRefactoring = new RefactorSwitchDictionary.Refactoring.StringToColor();
            Color fooRefactoringColor = fooRefactoring.Transfer(MyColorName);

            Console.WriteLine($"{MyColorName} =  A:{fooRefactoringColor.A} R:{fooRefactoringColor.R} G:{fooRefactoringColor.G} B:{fooRefactoringColor.B}");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
    }
}
執行結果內容
Console
使用 資料字典與多型 來重構方法,進行 Swith 需求設計
Blue =  A:255 R:0 G:0 B:255
Press any key for continuing...

關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程