你可以在结构中有一个类吗?
Posted
技术标签:
【中文标题】你可以在结构中有一个类吗?【英文标题】:Can you have a class in a struct? 【发布时间】:2010-09-09 06:41:12 【问题描述】:在 C# 中是否可以有一个带有类类型成员变量的 Struct?如果是这样,信息存储在哪里,在堆栈、堆或两者上?
【问题讨论】:
【参考方案1】:是的,你可以。指向类成员变量的指针与结构的其余值一起存储在堆栈上,而类实例的数据存储在堆上。
结构也可以包含类定义作为成员(内部类)。
这里有一些非常无用的代码,至少可以编译并运行以表明它是可能的:
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;
【讨论】:
只是好奇,你为什么要划掉堆栈?结构体不是在堆栈上存储所有数据,包括在这种情况下指向引用成员的指针吗? @JamesM blogs.msdn.microsoft.com/ericlippert/2009/04/27/… @user1618054 堆栈被划掉的事实是正确的。结构在堆栈上声明时将其值存储在堆栈上,但它们也可以由存储在堆上的类使用。例如,结构列表将保存堆上结构的内存。此外,当您进行迭代并且您在堆栈中有一个列表元素时,您所拥有的是存储在堆上的原始数据的副本。【参考方案2】:类内容存储在堆中。
对类的引用(几乎与指针相同)与结构内容一起存储。 struct 内容的存储位置取决于它是局部变量、方法参数还是类的成员,以及它是被装箱还是被闭包捕获。
【讨论】:
很高兴您提到存储因标识符的类型(局部变量、参数或成员)而异。 +1。 @Ben Voigt,那么,Struct 的堆栈分配的真正优势仅在于它既是局部变量又是方法参数时?我猜如果 Struct 以任何形式引用任何堆内存,它不会提供任何优势.. @Trident:我不会说优势在于堆栈分配。这是因为结构是“裸”数据。无需额外分配。没有关联的监视器。没有关联的 vtable。在垃圾收集期间无需触摸它。无论结构所在的较大数据结构是调用堆栈、数组、堆上的对象还是其他任何东西,这些都是正确的。 @Ben Voigt,感谢您的澄清,我得到了所有这些,但只有一个。 “在垃圾收集过程中无需触摸它”。我仍然不确定它是如何工作的。假设我有一个带有 Int 数组的 Struct,然后它在托管堆上分配但没有引用。当局部变量超出范围时,堆上的数组是不可达的,所以占用的 int 数据块也应该被收集进程释放,对吗?或者收集意味着只收集引用类型和数据类型,无论是它的 Class 还是 Struct 都不会被 GC 触及? @Trident:您对垃圾收集的工作原理有错误的概念。在丢弃该字符串实例之前,它必须搜索指向相同“string
存储在 0x1000”的任何其他对象。字符串数组在无法访问时会消失,即使其中引用的某些对象将继续存在。 struct 数组实际上包含它的元素,不涉及引用(指针),因此当数组不可达时,根据定义,元素也是不可达的,不需要在运行时进行分析来检查。【参考方案3】:
如果结构的字段之一是类类型,则该字段将保存类对象的 identity 或空引用。如果所讨论的类对象是不可变的(例如string
),则存储其身份也将有效地存储其内容。但是,如果所讨论的类对象是可变的,则存储标识将是存储内容的有效方法当且仅当引用永远不会落入任何代码的手中,一旦它存储在字段.
一般来说,除非出现以下两种情况之一,否则应避免在结构中存储可变类类型:
-
实际上,人们感兴趣的是类对象的身份而不是其内容。例如,可以定义一个 `FormerControlBounds` 结构,该结构包含 `Control` 和 `Rectangle` 类型的字段,并表示控件在某个时刻具有的 `Bounds`,以便以后能够恢复该控件到它之前的位置。 `Control` 字段的目的不是保存控件状态的副本,而是标识应该恢复其位置的控件。一般来说,结构应该避免访问它持有引用的对象的任何可变成员,除非这种访问很明显是指所讨论对象的当前可变状态(例如在`CaptureControlPosition`或` RestoreControlToCapturedPosition` 方法或 `ControlHasMoved` 属性)。
该字段是`private`,读取它的唯一方法是为了检查其属性而不将对象本身暴露给外部代码,并且写入它的唯一方法将创建一个新对象,执行所有将发生在它身上的突变,然后存储对该对象的引用。例如,可以设计一个“结构”,它的行为很像一个数组,但具有值语义,通过让结构在私有字段中保存一个数组,并通过每次尝试写入数组来创建一个包含数据的新数组从旧数组修改新数组,并将修改后的数组存储到该字段。请注意,即使数组本身是可变类型,但存储在该字段中的每个数组实例实际上都是不可变的,因为任何可能改变它的代码都无法访问它。
请注意,场景 #1 在泛型类型中很常见;例如,有一个字典,其“值”是可变对象的身份是很常见的;枚举该字典将返回 KeyValuePair
的实例,其 Value
字段包含该可变类型。
场景 #2 不太常见。唉,没有办法告诉编译器除了属性设置器之外的结构方法会修改结构,因此应该禁止在只读上下文中使用它们;可以有一个行为类似于List<T>
的结构,但具有值语义,并包含Add
方法,但尝试在只读结构实例上调用Add
会生成虚假代码而不是编译器错误.此外,这种结构上的变异方法和属性设置器通常会表现得很差。当此类结构作为不可变包装存在于其他可变类上时,它们可能很有用;如果这样的结构从不装箱,性能通常会比类好。如果只装箱一次(例如,通过转换为接口类型),性能通常与类相当。如果反复装箱,性能可能会比一个类差很多。
【讨论】:
【参考方案4】:可能不建议这样做:请参阅http://msdn.microsoft.com/en-us/library/ms229017(VS.85).aspx
引用类型在堆上分配,内存管理是 由垃圾收集器处理。
值类型在堆栈或内联上分配并被释放 当他们超出范围时。
一般来说,值类型的分配和解除分配成本更低。 但是,如果它们用于需要显着 装箱和拆箱的数量,与 引用类型。
【讨论】:
您能在回答中总结一下原因吗? (链接失效,等等。)以上是关于你可以在结构中有一个类吗?的主要内容,如果未能解决你的问题,请参考以下文章