在我們這篇文章將會以記憶體耗用的角度,來查看當進行類別繼承設計的時候,基底類別與延伸類別所產生的物件,到底會耗用多少的記憶體來進行觀察。
這篇文章將會建立一個名為 MeasureObjectSize 的 .NET Core 主控制台應用程式專案
首先,我們會宣告三個類別,一個是空的類別 MyEmptyClass,在這裡面甚麼都沒有宣告與定義,第二個是基底類別 MyBaseClase,他只有一個 Number 屬性並且為一個整數陣列,長度為 1024 個,第三個為衍生類別 MyDerivedClass,他繼承了 基底類別 MyBaseClase
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 位元組這個數值
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 的物件,並且得到他們分別耗用了多少記憶體空間,底下是我們的執行結果。
我們看到了,基底類別與衍生類別所產生的物件,他們具有相同的記憶體使用空間,你知道為什麼嗎?
量測單純繼承之物件使用記憶體使用情況
產生空類別 之執行個體耗用記憶體 : 24
建立基底類別之執行個體耗用記憶體 : 4144
建立衍生類別之執行個體耗用記憶體 : 4144
衍生類別之執行個體 與 基底類別之執行個體 記憶體相差數量 : 0
Press any key for continuing...
剛剛我們看到了衍生類別繼承了基底類別,不過,衍生類別裡面沒有另外新增任何類別成員,他們耗用相同的記憶體空間。現在,我們在基底類別的陣列屬性成員前,加入 virtual 關鍵字,並且在衍生類別內,使用 override 來覆寫這個屬性,那麼,對於由基底類別與衍生類別所產生出來的物件,其佔用記憶體情況又會是如何呢?
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];
}
}
現在,就讓我們也使用相同的技巧,分別建立起基底類別與衍生類別的物件,計算出這兩個物件到底佔用了多少記憶體空間。
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 長度的陣列整數記憶體空間,您能夠理解嗎?
量測繼承與多型覆寫之物件使用記憶體使用情況
產生空類別 之執行個體耗用記憶體 : 24
建立基底類別之執行個體耗用記憶體 : 4144
建立衍生類別之執行個體耗用記憶體 : 8272
衍生類別之執行個體 與 基底類別之執行個體 記憶體相差數量 : 4128
最後,我們在基底類別的屬性成員前面將不會加入任何的 virtual 關鍵字,並且在衍生類別屬性成員中宣告了 new 這個隱藏基底成員
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];
}
}
我們也使用了相同的記憶體使用量的量測技術,分別檢視基底類別與衍生類別他們所耗用的記憶體大小
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 隱藏關鍵字,這個時候,當衍生類別物件產生了時候,除了衍生類別中宣告的成員會占用記憶體之外,衍生類別所產生的物件,也會包含了基底類別的屬性成員所需要用到的記憶體大小。
量測繼承與多型隱藏之物件使用記憶體使用情況
產生空類別 之執行個體耗用記憶體 : 24
建立基底類別之執行個體耗用記憶體 : 4144
建立衍生類別之執行個體耗用記憶體 : 8272
衍生類別之執行個體 與 基底類別之執行個體 記憶體相差數量 : 4128
關於 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 課程