你可以在结构体中有一个类吗?
-
09-06-2019 - |
题
在 C# 中是否可以有一个带有类类型成员变量的结构?如果是这样,信息存储在哪里,在堆栈、堆还是两者上?
解决方案
是的你可以。存储的是类成员变量的指针 在堆栈上 与结构体的其余值,并且类实例的数据存储在堆上。
结构还可以包含类定义作为成员(内部类)。
这是一些非常无用的代码,至少可以编译并运行以表明它是可能的:
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;
}
}
}
}
其他提示
类内容存储在堆上。
对类的引用(几乎与指针相同)与结构内容一起存储。结构体内容的存储位置取决于它是局部变量、方法参数还是类成员,以及它是被装箱还是被闭包捕获。
如果结构体的某个字段是类类型,则该字段将保存 身份 类对象或空引用。如果所讨论的类对象是不可变的(例如 string
),存储其身份也将有效地存储其内容。但是,如果所讨论的类对象是可变的,则存储标识将是存储内容的有效方法 当且仅当引用永远不会落入任何代码手中,一旦将其存储在字段中,这些代码可能会改变它.
通常,应避免在结构中存储可变类类型,除非适用以下两种情况之一:
- 事实上,人们感兴趣的是类对象的身份而不是它的内容。例如,可以定义一个“FormerControlBounds”结构,其中包含“Control”和“Rectangle”类型的字段,并表示控件在某个时刻所具有的“Bounds”,以便以后能够恢复控件到其先前的位置。“Control”字段的目的不是保存控件状态的副本,而是标识应恢复其位置的控件。通常,结构体应避免访问它所引用的对象的任何可变成员,除非明确此类访问指的是相关对象的当前可变状态(例如在“CaptureControlPosition”或“RestoreControlToCapturedPosition”方法或“ControlHasMoved”属性中)。
- 该字段是“私有”,读取它的唯一方法是为了检查其属性,而不将对象本身暴露给外部代码,并且写入它的唯一方法将创建一个新对象,执行所有突变它将发生在它身上,然后存储对该对象的引用。例如,可以设计一个“结构”,其行为非常类似于数组,但具有值语义,通过让该结构在私有字段中保存一个数组,并让每次尝试写入该数组都创建一个包含数据的新数组从旧数组修改新数组,并将修改后的数组存储到该字段。请注意,即使数组本身是可变类型,存储在字段中的每个数组实例实际上都是不可变的,因为任何可能改变它的代码都永远无法访问它。
请注意,场景 #1 对于泛型类型来说非常常见;例如,有一个字典,其“值”是可变对象的标识是很常见的;枚举该字典将返回以下实例 KeyValuePair
谁的 Value
字段保存该可变类型。
场景 #2 不太常见。可惜没有办法告诉编译器除了属性设置器之外的结构体方法将修改结构体,因此应禁止在只读上下文中使用它们;人们可以有一个行为类似于 List<T>
, ,但具有值语义,并且包含 Add
方法,但尝试调用 Add
在只读结构实例上会生成虚假代码而不是编译器错误。此外,此类结构上的变异方法和属性设置器通常性能相当差。当此类结构作为可变类上的不可变包装器存在时,它们会很有用;如果这样的结构从未被装箱,那么性能通常会比类更好。如果恰好装箱一次(例如通过转换为接口类型),性能通常与类相当。如果反复装箱,性能可能会比班级差很多。
可能不建议这样做:看 http://msdn.microsoft.com/en-us/library/ms229017(VS.85).aspx
参考类型分配在堆上,内存管理由垃圾收集器处理。
价值类型分配在堆栈或内联上,并在范围范围内进行划分。
一般来说,值类型的分配和释放成本更便宜。但是,如果它们用于需要大量拳击和拆箱的场景,则与参考类型相比,它们的性能差。