.net托管内存如何处理对象内部的值类型?

时间:2020-03-05 18:42:32  来源:igfitidea点击:
public class MyClass
{
    public int Age;
    public int ID;
}

public void MyMethod() 
{
    MyClass m = new MyClass();
    int newID;
}

据我了解,以下是正确的:

  • 引用m位于堆栈上,并且在MyMethod()退出时超出范围。
  • 值类型newID驻留在堆栈上,并在MyMethod()退出时超出范围。
  • 假设不存在对该对象的其他引用,则由新运算符创建的对象将驻留在堆中,并且在MyMethod()退出时可由GC回收。

这是我的问题:

  • 对象中的值类型是否存在于堆栈或者堆中?
  • 装箱/拆箱对象中的值类型是否值得关注?
  • 是否有关于此主题的详细但可理解的资源?

从逻辑上讲,我认为类内的值类型应该在堆中,但是我不确定是否必须将它们装箱才能到达那里。

编辑:

建议阅读该主题:

  • 杰弗里·里希特(Jeffrey Richter)通过C#进行CLR
  • Don Box的Essential .NET

解决方案

回答

  • 对象拥有的所有引用或者值类型都存在于堆中。
  • 仅当我们将int强制转换为Objects时。

回答

我所见过的最好的资源是Cby Jeffrey Richter所著的CLR。如果我们进行任何.NET开发,都非常值得一读。基于该文本,我的理解是引用类型内的值类型确实存在于父对象中嵌入的堆中。引用类型总是在堆上。
装箱和拆箱不是对称的。与拆箱相比,装箱可能是更大的问题。装箱将需要将值类型的内容从堆栈复制到堆。根据发生这种情况的频率,可能没有必要使用结构而不是类。如果我们有一些对性能有严格要求的代码,并且不确定装箱和拆箱是否正在发生,请使用工具检查我们方法的IL代码。我们会在IL中看到单词框和框框。就个人而言,我将评估代码的性能,然后再查看是否值得担心。就我们而言,我认为这不是一个关键问题。每次访问引用类型内的该值类型时,我们都不必从堆栈复制到堆(盒子)。在这种情况下,拳击成为更有意义的问题。

回答

Do value types within objects live on the stack or the heap?

在堆上。它们是对象覆盖区分配的一部分,就像保存引用的指针一样。

Is boxing/unboxing value types in an object a concern?

这里没有拳击。

Are there any detailed, yet understandable, resources on this topic?

对Richter的书+1票。

回答

类的值类型值必须与对象实例一起存在于托管堆中。方法的线程堆栈仅在方法持续时间内有效;如果值仅存在于该堆栈中,该值如何持久?

托管堆中类的对象大小是其值类型字段,引用类型指针和其他CLR开销变量(如Sync块索引)的总和。当人们为一个对象的value-type字段分配一个值时,CLR将该值复制到该对象内为该特定字段分配的空间。

例如,一个具有单个字段的简单类。

public class EmbeddedValues
{
  public int NumberField;
}

并带有一个简单的测试类。

public class EmbeddedTest
{
  public void TestEmbeddedValues()
  {
    EmbeddedValues valueContainer = new EmbeddedValues();

    valueContainer.NumberField = 20;
    int publicField = valueContainer.NumberField;
  }
}

如果我们使用.NET Framework SDK提供的MSIL反汇编程序来查看EmbeddedTest.TestEmbeddedValues()的IL代码,

.method public hidebysig instance void  TestEmbeddedValues() cil managed
{
  // Code size       23 (0x17)
  .maxstack  2
  .locals init ([0] class soapextensions.EmbeddedValues valueContainer,
           [1] int32 publicField)
  IL_0000:  nop
  IL_0001:  newobj     instance void soapextensions.EmbeddedValues::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldc.i4.s   20
  IL_000a:  stfld      int32 soapextensions.EmbeddedValues::NumberField
  IL_000f:  ldloc.0
  IL_0010:  ldfld      int32 soapextensions.EmbeddedValues::NumberField
  IL_0015:  stloc.1
  IL_0016:  ret
} // end of method EmbeddedTest::TestEmbeddedValues

请注意,CLR被告知将堆栈中的装入值" 20" stfld装入装入的EmbeddValues的NumberField字段位置,直接放入托管堆中。同样,在检索值时,它使用ldfld指令将值直接从该托管堆位置复制到线程堆栈中。这些类型的操作不会进行装箱/拆箱。

回答

  • 答案1:堆。从Don Box出色的《 Essential .Net Vol 1》中释义
Reference Types(RT) always yield instances that are allocated on the heap. In contrast, value types(VT) are dependent on the context - If a local var is a VT, the CLR allocates memory on the stack. If a field in a class is a member of a VT, then the CLR allocates memory for the instance as part of the layout of the object/Type in which field is declared.
  • 答案2:否。仅当我们通过对象引用/接口指针访问结构时,才会进行装箱。 obInstance.VT_typedfield不会装箱。
RT variables contains the address of the object it refers to. 2 RT var can point to the same object. In contrast, VT variables are the instances themselves. 2 VT var cannot point to same object(struct)
  • 答案3:Don Box的Essential .net /杰弗里·里希特(Jeffrey Richter)通过C#进行的CLR。我有前者的副本...尽管以后可能会针对.Net修订版进行更多更新