public class MyClass
{
    public int Age;
    public int ID;
}

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

据我了解,以下情况属实:

  1. 引用 m 存在于堆栈中,并在 MyMethod() 退出时超出范围。
  2. 值类型 newID 存在于堆栈中,并在 MyMethod() 退出时超出范围。
  3. 由 new 运算符创建的对象位于堆中,并且当 MyMethod() 退出时,可以由 GC 回收(假设不存在对该对象的其他引用)。

这是我的问题:

  1. 对象中的值类型位于堆栈还是堆上?
  2. 对象中的装箱/拆箱值类型是否值得关注?
  3. 有关于这个主题的详细且易于理解的资源吗?

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

编辑:

本主题的建议阅读:

  1. 通过 C# 进行 CLR 作者:Jeffrey Richter
  2. Don Box 的 Essential .NET
有帮助吗?

解决方案

的值类型值与托管堆中的对象实例一起生成。方法的线程堆栈仅在方法的持续时间内存在;如果值仅存在于该堆栈中,该值如何保持不变?

托管堆中的类'对象大小是其值类型字段,引用类型指针和其他CLR开销变量(如同步块索引)的总和。当一个值为一个对象的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被告知 stfld 加载的值“20”。在堆栈中加载EmbeddValues的NumberField字段位置,直接进入托管堆。类似地,在检索值时,它使用 ldfld 指令将该托管堆位置的值直接复制到线程堆栈中。这些类型的操作不会发生框/拆箱。

其他提示

  1. 对象拥有的任何引用或值类型。
  2. 仅当您向对象投射时。

我见过的最好的资源是杰弗里里希特的书籍CLR by C#。如果您进行任何.NET开发,那么非常值得一读。基于该文本,我的理解是引用类型中的值类型确实存在于父对象中嵌入的堆中。堆上的引用类型始终。 拳击和拆箱不对称。拳击比拆箱更重要。 Boxing 要求将值类型的内容从堆栈复制到堆。根据这种情况发生的频率,有一个结构而不是一个类可能没有意义。如果您有一些性能关键代码并且您不确定是否正在进行装箱和拆箱,请使用工具检查方法的IL代码。你会在IL中看到单词box和unbox。就个人而言,我会衡量我的代码的性能,然后再看看这是否是一个担心的候选人。在你的情况下,我不认为这将是一个如此关键的问题。每次在引用类型中访问此值类型时,您都不必从堆栈复制到堆(框)。那种情况是拳击成为一个更有意义的问题。

  • 答案#1:堆。释义 Don Box 的精彩“Essential .Net Vol 1”

引用类型 (RT) 始终生成在堆上分配的实例。 相反,值类型 (VT) 取决于上下文 - 如果本地变量是 VT,则 CLR 在堆栈上分配内存。如果类中的字段是 VT 的成员,则 CLR 会为实例分配内存,作为声明字段的对象/类型布局的一部分。

  • 答案#2:不。仅当您通过对象引用/接口指针访问结构时才会发生装箱。 obInstance.VT_typedfield 不会装箱。

    RT变量包含它所引用的对象的地址。2个RT var可以指向同一个对象。 相反,VT 变量就是实例本身。2 VT var 不能指向同一个对象(结构)

  • 答案#3:Don Box 的 Essential .net / Jeffrey Richter 通过 C# 的 CLR。我有一份以前的副本...尽管后者可能会针对 .Net 修订版进行更多更新

  

对象中的值类型是否存在于堆栈或堆上?

在堆上。它们是对象足迹分配的一部分,就像保持引用的指针一样。

  

对象中的装箱/取消装箱值类型是否值得关注?

这里没有拳击。

  

这个主题是否有任何详细但可理解的资源?

给Richter的书+1票。

结构类型的变量或其他存储位置是该类型的公共和私有实例字段的聚合。给定

struct Foo {public int x,y; int z;}

声明 Foo bar; 将导致 bar.x bar.y bar.z 存储在 bar 的任何地方。从存储布局的角度来看,将 bar 的这种声明添加到类中将等同于添加三个 int 字段。实际上,如果除了访问其字段之外从未对 bar 做任何事情, bar 的字段的行为与三个字段 bar_x 的行为相同,<代码> bar_y 和 bar_cantaccessthis_z [访问最后一个需要使用 bar 进行操作而不是访问其字段,但无论是否占用空间都会占用空间它实际上用于任何事情]。

将结构类型存储位置识别为字段聚合是理解结构的第一步。试图将它们视为持有某种对象可能看起来“更简单”,但与实际工作方式不符。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top