30

在 C# 中是否可以有一个带有类类型成员变量的 Struct?如果是这样,信息存储在哪里,在堆栈、堆或两者上?

4

4 回答 4

28

是的你可以。指向类成员变量的指针与结构的其余值一起存储在堆栈中,类实例的数据存储在堆中。

结构也可以包含类定义作为成员(内部类)。

这是一些非常无用的代码,至少可以编译并运行以表明它是可能的:

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            MyStr m = new MyStr();
            m.Foo();

            MyStr.MyStrInner mi = new MyStr.MyStrInner();
            mi.Bar();

            Console.ReadLine();
        }
    }

    public class Myclass
    {
        public int a;
    }

    struct MyStr
    {
        Myclass mc;

        public void Foo()
        {
            mc = new Myclass();
            mc.a = 1;
        }

        public class MyStrInner
        {
            string x = "abc";

            public string Bar()
            {
                return x;
            }
        }
    }
}
于 2008-09-16T01:53:07.223 回答
17

类内容存储在堆上。

对类的引用(几乎与指针相同)与结构内容一起存储。结构内容的存储位置取决于它是局部变量、方法参数还是类的成员,以及它是被装箱还是被闭包捕获。

于 2011-07-23T15:53:20.620 回答
4

如果结构的字段之一是类类型,则该字段将保存类对象的标识或空引用。如果所讨论的类对象是不可变的(例如string),则存储其身份也将有效地存储其内容。但是,如果所讨论的类对象是可变的,则存储标识将是存储内容的一种有效方法,当且仅当引用永远不会落入一旦存储在 field 中可能会改变它的任何代码的手中

通常,除非以下两种情况之一适用,否则应避免在结构中存储可变类类型:

  1. 实际上,人们感兴趣的是类对象的身份而不是其内容。例如,可以定义一个 `FormerControlBounds` 结构,该结构包含 `Control` 和 `Rectangle` 类型的字段,并表示控件在某个时刻具有的 `Bounds`,以便以后能够恢复该控件到它之前的位置。'Control' 字段的目的不是保存控件状态的副本,而是标识应该恢复其位置的控件。一般来说,结构应该避免访问它持有引用的对象的任何可变成员,除非这种访问很明显是指所讨论对象的当前可变状态(例如在`CaptureControlPosition`或` RestoreControlToCapturedPosition` 方法,
  2. 该字段是“私有”的,读取它的唯一方法是为了检查其属性而不将对象本身暴露给外部代码,并且写入它的唯一方法将创建一个新对象,执行所有突变这将发生在它身上,然后存储对该对象的引用。例如,可以设计一个“结构”,它的行为很像一个数组,但具有值语义,通过让结构在私有字段中保存一个数组,并通过每次尝试写入数组来创建一个包含数据的新数组从旧数组修改新数组,并将修改后的数组存储到该字段。请注意,即使数组本身是可变类型,但存储在该字段中的每个数组实例实际上都是不可变的,

请注意,场景 #1 在泛型类型中很常见;例如,有一个字典,其“值”是可变对象的身份是很常见的;枚举该字典将返回KeyValuePairValue字段包含该可变类型的实例。

场景 #2 不太常见。唉,没有办法告诉编译器除了属性设置器之外的结构方法会修改结构,因此应该禁止在只读上下文中使用它们;可以有一个行为类似于 a 的结构List<T>,但具有值语义,并包含一个Add方法,但尝试调用Add在只读结构实例上会生成虚假代码而不是编译器错误。此外,这种结构上的变异方法和属性设置器通常会表现得很差。当此类结构作为不可变包装存在于其他可变类上时,它们可能很有用;如果这样的结构从不装箱,性能通常会比类好。如果只装箱一次(例如通过转换为接口类型),性能通常与类相当。如果反复装箱,性能可能会比班级差得多。

于 2012-08-09T16:14:14.513 回答
2

这样做可能不是推荐的做法:请参阅http://msdn.microsoft.com/en-us/library/ms229017(VS.85).aspx

引用类型在堆上分配,内存管理由垃圾收集器处理。

值类型在堆栈或内联上分配,并在超出范围时被释放。

通常,值类型的分配和解除分配成本更低。但是,如果在需要大量装箱和拆箱的场景中使用它们,则与引用类型相比,它们的性能较差。

于 2008-09-16T02:12:00.640 回答