具有引用成员的结构体:堆还是栈?

6
众所周知,struct 是一种值类型,因此在堆栈上分配(除了特定情况下它被装箱到类中)。
然而让我们考虑这个struct
public struct TestStruct
{
    public List<int> items;
}

internal class Program
{
    private static void Main(string[] args)
    {
        TestStruct g;
    }
}

我们的TestStruct g不是类的成员,而是在Main函数中声明的“独立”变量。它符合分配在stack上的变量的要求。

然而,如果我写成:

g.items = new List<int>();
  1. 我猜items在堆上分配,g也在堆上吗?
  2. g超出范围后,items会发生什么(即GC是否必须为items执行其工作)?
  3. 如果我们有一个实现IDisposable的类型的变量,而不是List<int>,最好的做法是什么?

PS:我知道在这种情况下应该使用class而不是struct。我只是被这个特定案例搞糊涂了。


我认为在微软的指南中有这样一条规定:包含 IDisposable 成员的类/结构体也应该是 IDisposable 本身 - 这样你就可以在 Dispose 中清理可处理的成员。 - Patryk Ćwiek
第一个子问题也在在C#中结构体中的引用类型中得到了回答。 - user395760
这个人讲得非常好:http://www.youtube.com/playlist?list=PLRwVmtr-pp07XP8UBiUJ0cyORVCmCgkdA - Dennis_E
2个回答

7
我想您在堆上分配了items吧?
是的。items的内存将在堆上分配。
g也会在堆上吗?
不,结构体留在堆栈中。它只有一个字段,用于保存堆上items列表的引用。
当g超出范围时,对于items会发生什么情况(即GC是否必须为items做其工作)?
如果g超出范围,则应用程序根中不会有关于items的引用。项目将成为垃圾,并将在下一次垃圾收集期间被GC收集。在那之前,items将保留在内存中(使用它的方法退出后,结构体实例将被删除)。
如果我们不是List而是IDisposable类型的变量,最好采取什么行动?
最佳措施是通过结构体来实现IDisposable。更新:实际上,如@MarcGravell所指出-如果可能,最好不要在这种情况下使用结构体。

2
在我看来,拥有一个实现 IDisposable 的结构体是一个非常糟糕的想法;这样做没有任何好处。如果需要将其实现为无操作以满足某些通用约束,那么或许可以考虑。但对于实际的功能代码来说?非常不可靠。 - Marc Gravell
@MarcGravell,我同意你的观点 - 我从未使用过实现接口的结构体(坦白地说,我很少使用结构体),但是如果您有一个带有可处理对象的结构体,那么创建类的最佳替代方案就是实现接口(至少现代生产力工具会提醒您需要处理它)。但同意应该使用而不是结构体。 - Sergey Berezovskiy
这取决于接口是否具有幂等性。对于诸如IEquatable<T>IComparable等的接口,不期望接口会改变状态 - 但是对于Dispose()方法来说则完全不同。如果在使用struct时,这会变得非常令人困惑,因为你需要考虑“我编辑的是哪个结构体的副本?是否还有其他副本,它们并知道自己已经被处理?”等问题。 - Marc Gravell

4
因此它被分配在堆栈上(除了特定情况,例如封装在类中的情况),是错误的。它作为声明范围的一部分进行分配。实际上,有更多场景将其分配在堆而不是栈上(简单地说:作为另一个对象的一部分 - 而不是对象本身),因此这不是一个好的规则。
针对你的具体问题:
1. items所指的对象(new List ())放在堆上;field items是struct的一部分,在任何地方都可以(并仅保持引用-本质上是一个精美的指针) 2. 当所有引用超出范围时,确实会由GC考虑该对象 3. 这取决于谁拥有对象的生命周期;如果是TestStruct实例,则您最好将TestStruct实际上是实现IDisposable的类,并从类的Dispose()中调用Dispose()
额外的想法是:能够编写g.items = new List (); 对我来说表明这是一个非常糟糕的结构选择,因为可变性和struct不能友好地发挥作用(可能存在许多意外错误)。要么:
- 使struct成为不可变的(即只读字段,在自定义构造函数中初始化) - 将其作为class处理
在任何一种情况下,公共字段都是一个不好的选择,它应该是一个具有get的属性(如果它是class,则可能是set-但如果它仍然是struct,则可能不是)。
示例:
public struct TestStruct {
    private readonly List<int> items;
    public List<int> Items { get { return items; } }
    public TestStruct(List<int> items) {
        this.items = items;
    }
}

或者:

public sealed class TestClass : IDisposable {
    private SomeDisposable items = new SomeDisposable();
    public SomeDisposable Items { get { return items; } }
    public void Dispose() {
        if(items != null) {
            items.Dispose();
            items = null;
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接