2016-02-03 39 views
1

問題的目的是瞭解繼承如何在內存中工作;我知道它是什麼以及何時使用它。C#.NET - 繼承 - 在內存中創建的對象的數量

以下是用例 -

class A {} 

class B : A {} 

class C 
{ 
B b = new B(); 
} 

現在,有多少個對象(排除一個爲C級,因爲這將是切入點和任何違約DOTNET的/ CLR對象)在內存中創建?它是兩個(一個是A,另一個是B)?或者只有一個B也包含A的成員?一些解釋會有所幫助。

+7

在你的例子0中,因爲你沒有創建一個C的實例嗎? – BlueTrin

+1

你爲什麼在意?有沒有什麼你做的工作會有所不同,取決於框架內部如何處理?我不完全確定,但我認爲在.NET框架下,如何繼承工作不契約。 – Luaan

+0

@BlueTrin:上面的代碼只是爲了顯示類之間的關係並添加更多信息。開發人員通常比英語更好地理解代碼。無論如何感謝您的快速回復。 –

回答

1

既然你的問題的目的似乎是基本上看到使用構圖和繼承之間的「物理」差異,我將關注這一點。

當您使用new時,將創建一個類型的單個實例,並且該類型的適當構造函數(及其所有「父項」)被執行一次。

在C#中,默認繼承方法(B : A類型)是的子類化。在這種方法中,派生類基本上是它的父類的副本,加上派生類的實例字段以及與例如與其關聯的元數據。任何新的虛擬方法。

這意味着在你的情況下,調用new B()只會創建一個對象實例,就是這樣。 B的實例本身包含A的字段和元數據,但不包含參考A的實例。

如果定義B2這樣的:

class A2 
{ 
    int myInt; 
} 

class B2 
{ 
    A2 aInstance = new A2(); 
} 

然後B2構造還創建的A2一個實例,因此你有兩個對象實例,A2類型之一,另一個B2類型。 B2僅包含對A2實例的引用,而不是其副本。

這是如何轉化爲運行時成本的?

  • 第二種方法意味着一個間接層。這可能會影響數據的局部性,但由於.NET分配方式的原因,通常情況下不會這樣 - 實際上,A2傾向於分配在B2之後。
  • 第二種方法意味着您需要一些額外的元數據,因爲您有兩個實例而不是一個。這基本上意味着一個指向類型句柄和同步塊索引的指針。這是每個實例的固定成本 - 同步塊爲4個字節,類型句柄爲4個字節。我不確定這是否會在64位上發生變化。除非您的實例的實例數據非常少,否則這不是一項巨大的成本。但我確定這不是合同性的,實際上,實際的最小大小是12個字節,而不是8個(或者至少它曾經是早期的GC)。
  • 第二種方法意味着GC需要擔心的額外實例。我不確定這在實踐中會產生多大的影響 - GC仍然需要經歷相同數量的內存,而且我認爲這對實踐中的GC性能比對象數量更重要。但這只是我的球場估計:)
  • 兩者的分配成本應該幾乎相同,提供實例元數據的少數額外字節。 .NET堆分配實際上更像堆棧分配 - 你只需要移動一個指針即可。這不太可能有所作爲,尤其是與收集和壓縮內存的成本相比:)

結果呢?那麼,我不認爲這是你需要提前關心的事情。有額外的實例需要花費,但除非你分配了數百萬個實例,否則它可能不會帶來很大的可觀察的差異。如果你的應用程序允許它,你甚至可以獲得淨收益,因爲組合模型可以讓你在多個地方重複使用同一個實例,這對於子類來說是不可能的。有時這是有道理的,有時它不會:)

當然,請注意,您並不總是必須使用類。例如,A2可以很容易地成爲struct,消除了額外的實例 - 再次,不可能與子類化,因爲struct s不能被繼承。在這種情況下,這兩種方法相當。

與通常情況下的性能一樣,您確實需要做實際的分析才能得到您的答案。結果可能會像「99.9%的代碼執行得很好,但如果我們將其更改爲struct並將多態性移到更高層,這一類可以爲我們節省大量CPU/RAM」。

最後,我很確定這些都不是合同的一部分。如果微軟決定在未來的.NET框架版本中改變繼承方式,並且創建一個新的實例,而不是「內聯」父代,我認爲它不會以任何方式違反規範。除非你絕對需要需要來依賴這個信息,否則不要。

1

如果忽略C(如程序入口)會有一個B對象和描述AB類2個System.RuntimeType對象。

通過C#,第四版參考CLR,P100-110

+0

爲什麼?! 我的答案和Meirion的是一樣的,我提到了「通過C#的CLR」,這是這些作品中被推崇的參考。 –

+1

如果你忽略C作爲程序入口,那麼你將有兩種反射類型,而不是三種。我提出了一個可能使它更清晰的編輯 –

+0

正如我之前所說的那樣,只會有一個對象。和三個類型的對象。 您使用'new'關鍵字創建了b。所以只會有一個對象。如果你讀了我告訴你的書,你會明白的很好 –

4

對於參數的緣故可以說你做的C

var c = new C(); 

一個實例在這一點上,你有兩個對象實例,因爲在施工期間C作出B的實例。

要回答你的問題,你有一個C的實例和一個B的實例。您沒有A的實例,即使B源自A。 (更新:忽略C任何reflection,你有B一個對象實例。)

你可以用一些代碼證明了這一點:

class A { } 

class B : A { } 

class C 
{ 
    public B B = new B(); 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var c = new C(); 

     var b = c.B; 

     var BasA = (A)b; 

     bool BisA = BasA.GetType() == typeof (A); 

     Console.WriteLine($"Assert That B is not A: {!BisA}"); 
    } 
} 

另外,您可以通過調試器看到所有你的記憶:

enter image description here

另外要小心這些術語。 ABC是類別。對象是實例的類。在C#中,描述一個類的信息可以封裝在一個System.Type類的實例中。

那麼讓我們稍微下去兔子洞吧; 在執行程序集中,有多少對象在內存中?

限制我們的範圍,只有那些班,除了從instanciating C得到兩個對象,你也將有System.RuntimeType一個爲ABC 3個實例:

var assemblyTypes = Assembly.GetExecutingAssembly().GetTypes(); 

    foreach (var classType in assemblyTypes) 
     Console.WriteLine("Type instance: " + classType); 

enter image description here

再次,這是顯示你如何有三個實例System.RuntimeType,即描述ABC

不是學究氣十足,你也將獲得的RuntimeAssembly(您可執行文件)和RuntimeType一個實例(您控制檯程序類),以及其他創建

+0

Meirion Hughes:+1獲得詳細的答案。 EXCLUDE用於C類,因爲它將是入口點和任何默認的DotNet/CLR/System.RuntimeType對象。如果我們只考慮A和B,那麼答案是什麼?在你的答案中,你最初說的是「1」,但你的控制檯輸出還顯示了A.的實例。 –

+0

如果你忽略C和其他所有的東西,你有1個對象實例'B',那就是基本答案。在現實和實際的術語中,所有的類都是由'RuntimeType'定義的,因此是內存中的對象。我已經更新了答案以反映這一點。 –

1

新對象時使用的關鍵字

源代碼更像是一個藍圖,其中爲了簡單起見,您指定一個對象擴展行爲和另一個對象的成員(繼承它)。這些信息是描述類型本身所必需的。實際的對象是通過使用關鍵字新的關鍵字基於類型的描述構建的。

在你的情況下,只會創建一個對象。