简体   繁体   English

C#.NET-继承-在内存中创建的对象数

[英]C#.NET - Inheritance - Number of objects created in memory

Purpose of question is to understand how inheritance works under the hood; 问题的目的是了解继承是如何在后台进行的。 I am aware about what it is and when to use it. 我知道它是什么以及何时使用。

Following is use case - 以下是用例-

class A {}

class B : A {}

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

Now, how many objects ( EXCLUDING the one for C class as it will be entry point and any default DotNet/CLR objects ) are created in memory? 现在,有多少个对象( 不包括一个用于C类,因为它会进入点和任何违约DOTNET的/ CLR对象 )在内存中创建? Is it two (one for A and other for B)? 是两个吗(一个代表A,另一个代表B)? Or is it only one for B which also contains members of A? 还是只有一个包含A成员的B成员? Some explanation will help. 一些解释会有所帮助。

For the sake of argument lets say you make an instance of C 为了论证,可以说您创建了C一个实例

var c = new C();

at this point you have two object instances, because during construction C makes an instance of B . 此时,您有两个对象实例,因为在构造过程中C创建了B的实例。

To answer your question, You have an instance of C and an instance of B . 要回答您的问题,您有一个C实例和一个B实例。 You don't have an instance of A , even though B is derived from A . 即使B是从A派生的,您也没有A的实例。 ( Update: Ignoring C and any reflection , you have one object instance of B . ) 更新:忽略C和任何反射 ,您只有一个B对象实例。)

You can prove this with some code: 您可以使用一些代码来证明这一点:

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}");
    }
}

Plus, You can see all your memory via the debugger: 另外,您可以通过调试器查看所有内存:

在此处输入图片说明

Also be careful with terminology. 还要注意术语。 A , B and C are classes. ABC是类。 Objects are instances of classes. 对象是类的实例 In C# the information that describes a class can be encapsulated in an instance of a System.Type class. 在C#中,描述类的信息可以封装在System.Type类的实例中。

So lets go down the rabbit hole a little; 因此,让我们稍微往兔子洞里走; How many objects do you have in memory, in a executing assembly? 在执行的程序集中,您的内存中有多少个对象?

Limiting our scope to those classes only, in addition to the two objects you get from instanciating C , you will also have three instance of System.RuntimeType one for A , B and C : 将我们的范围限制为仅这些类,除了实例化C所获得的两个对象之外,您还将拥有System.RuntimeType三个实例,其中ABCA

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

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

在此处输入图片说明

Again, this is showing how you have three instance of System.RuntimeType , that describe the classes A , B and C . 同样,这显示了您如何拥有三个System.RuntimeType实例,它们描述 ABC

Being pedantic, you will also get an instance of RuntimeAssembly (for your executable) and RuntimeType (for your console Program class), as well as others 脚踏实地,您还将获得RuntimeAssembly (对于您的可执行文件)和RuntimeType (对于您的控制台Program类)的实例,以及其他实例。

If you ignore C (as program entry) there will be one B object and two System.RuntimeType objects that describe the A and B classes. 如果忽略C (作为程序条目),将有一个B对象和两个描述AB类的System.RuntimeType对象。

Refer to CLR via C#, Fourth edition, P100-110 通过C#参阅CLR,第四版,P100-110

New objects are created when you use keyword new . 使用关键字new会创建新对象。

The source code is more like a blueprint where for simplicity you specify that one object extends behavior and members of another object (inherits it). 源代码更像一个蓝图,为简单起见,您可以指定一个对象扩展行为并扩展另一个对象的成员(继承它)。 This information is needed for describing the type itself. 需要此信息来描述类型本身。 The actual object is constructed based on the description of the type by using the new keyword. 通过使用new关键字根据类型的描述构造实际对象。

In your case only one object will be created. 在您的情况下,将仅创建一个对象。

Since it seems that the purpose of your question is to basically to see the "physical" difference between using composition and inheritance, I'm going to focus on that. 既然您的问题的目的似乎是基本上了解使用合成和继承之间的“物理”区别,所以我将重点介绍这一点。

When you use new , a single instance of the type is created, and the appropriate constructor of the type (and all of its "parents") is executed once. 当您使用new ,将创建该类型的单个实例,并且该类型的适当构造函数(及其所有“父代”)将执行一次。

In C#, the default inheritance approach (the B : A kind) is subclassing . 在C#中,默认继承方法( B : A一种)是子类化 In this approach, a derived class is basically a copy of its parent, plus the instance fields of the derived class, plus the metadata associated with eg any new virtual methods. 在这种方法中,派生类基本上是其父级的副本,再加上派生类的实例字段,再加上与例如任何新的虚拟方法关联的元数据。

This means that in your case, calling new B() only creates a single object instance, and that's it. 这意味着在您的情况下,调用new B()仅创建一个对象实例,仅此而已。 The instance of B contains within itself the fields and metadata of A , but not a reference to an instance of A . B的实例本身包含A的字段和元数据,但不包含 A实例的引用

If you define B2 like this: 如果您这样定义B2

class A2
{
  int myInt;
}

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

Then the B2 constructor also creates an instance of A2 , so you have two object instances, one of type A2 , another of type B2 . 然后, B2构造函数还创建A2的实例,因此您有两个对象实例,一个是A2类型,另一个是B2类型。 B2 only contains a reference to the A2 instance, rather than a copy of it. B2仅包含对A2实例的引用,而不是其副本。

How does this translate to runtime costs? 这如何转化为运行时成本?

  • The second approach means a layer of indirection. 第二种方法意味着间接层。 This can impact data locality, though not in the usual case, due to the way .NET allocation works - in practice, A2 will tend to be allocated right behind B2 . 由于.NET分配的工作方式,这可能会影响数据的位置(尽管在通常情况下不会),实际上, A2往往会被分配在B2
  • The second approach means you'll need a bit of extra metadata, since you have two instances instead of one. 第二种方法意味着您将需要一些额外的元数据,因为您有两个实例而不是一个。 This basically means a pointer to a type handle and a syncblock index. 这基本上意味着指向类型句柄和syncblock索引的指针。 This is a fixed cost per instance - 4 bytes for the syncblock, 4 bytes for the type handle. 这是每个实例的固定成本-同步块为4字节,类型句柄为4字节。 I'm not sure if this changes on 64-bit or not. 我不确定这是否会在64位上更改。 Unless your instances have very little instance data, this is not a huge cost. 除非您的实例具有很少的实例数据,否则这不是一个巨大的代价。 I'm pretty sure this is not contractual, though, and in fact, the actual minimum size is 12 bytes, rather than 8 (or at least it used to be with early GCs). 我非常确定这不是合同规定的,实际上,实际的最小大小是12个字节,而不是8个字节(或者至少在早期的GC中是这样)。
  • The second approach means an extra instance for the GC to worry about. 第二种方法意味着GC需要担心的一个额外实例。 I'm not sure how much of an impact this can have in practice - the GC still has to go through the same amount of memory, and I think that's more important for GC performance in practice than the amount of objects. 我不确定这在实践中会产生多大的影响-GC仍必须经历相同的内存量,并且我认为这对于实践中的GC性能比对象数量更重要。 But that's just my ballpark estimate :) 但这只是我的估计:)
  • The allocation cost of both should be pretty much the same, affording for the few extra bytes of instance metadata. 两者的分配成本应该几乎相同,以提供实例元数据的几个额外字节。 .NET heap allocations are really more like stack allocations - you just shift a pointer. .NET堆分配实际上更像是堆栈分配-您只需移动一个指针即可。 This is unlikely to make a difference, especially compared to the cost of collecting and compacting the memory :) 这不太可能产生变化,特别是与收集和压缩内存的成本相比:)

The result? 结果? Well, I don't think it's something you need to care about much in advance. 好吧,我认为这不是您需要事先关心的事情。 There is a cost to having the extra instances, but unless you're allocating millions of instances, it probably isn't going to make much of an observable difference. 拥有额外的实例是有代价的,但是除非您要分配数百万个实例,否则可能不会产生太大的可观的改变。 If your application allows it, you might even have a net gain, since the composition model can allow you to reuse the same instance in multiple places which simply isn't possible with subclassing. 如果您的应用程序允许,您甚至可能会获得纯收益,因为合成模型可以使您在多个地方重用同一实例,而子类化根本不可能。 Sometimes this makes sense, sometimes it doesn't :) 有时候这很有意义,有时候却没有:)

And of course, note that you don't always have to use classes. 当然,请注意,您不必总是使用类。 For example, A2 can easily be a struct , eliminating the extra instance - again, impossible with subclassing, since struct s can't be inherited from. 例如, A2可以很容易地成为struct ,从而消除了多余的实例-再次,这对于子类是不可能的,因为struct不能从那里继承。 In that case, the two approaches become equivalent. 在这种情况下,这两种方法变得等效。

As is usually the case with performance, you really need to do practical profiling to get your answer. 与性能通常一样,您确实需要进行实用的性能分析以获取答案。 And the result will probably be something like "99.9% of the code performs fine, but this one class could save us a lot of CPU/RAM if we changed it to a struct and moved the polymorphism to a higher layer". 结果可能是“ 99.9%的代码执行得很好,但是如果我们将其更改为struct并将多态性移到更高的层,这一类可以为我们节省很多CPU / RAM”。

Finally, I'm pretty sure none of this is part of the contract. 最后,我很确定这些都不是合同的一部分。 If Microsoft decides to change the way inheritance works in a future version of the .NET framework, and makes it create a new instance instead of "inlining" the parent, I don't think it's going to violate the specification in any way. 如果Microsoft决定在.NET Framework的未来版本中更改继承的工作方式,并使其创建一个新实例,而不是“内联”父级,则我认为它不会以任何方式违反规范。 Unless you absolutely need to depend on this information, don't. 除非您绝对需要依赖此信息,否则不要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM