关于c#:您可以在struct中有一个类吗?

关于c#:您可以在struct中有一个类吗?

Can you have a class in a struct?

在C#中是否可能有一个Struct,且其成员变量是Class类型?如果是这样,信息将存储在堆栈,堆或两者上的什么位置?


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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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;
            }
        }
    }
}

类内容存储在堆中。

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


如果struct的字段之一是类类型,则该字段将包含类对象的标识或null引用。如果所讨论的类对象是不可变的(例如string),则存储其标识也将有效地存储其内容。但是,如果所讨论的类对象是可变的,则当且仅当引用永远不会落入任何一旦将其存储在字段中就可能使它发生变化的代码之手时,存储身份将是存储内容的有效方法。

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

  • 实际上,人们感兴趣的是类对象的身份而不是其内容。例如,可以定义一个" FormerControlBounds"结构,该结构包含" Control"和" Rectangle"类型的字段,并表示控件在某个时刻具有的" Bounds",以便以后可以还原控件。恢复到先前的位置。"控件"字段的目的不是保存控件状态的副本,而是标识应恢复其位置的控件。通常,该结构应避免访问其持有引用的对象的任何可变成员,除非很明显这种访问是指所讨论对象的当前可变状态(例如,在" CaptureControlPosition"或" RestoreControlToCapturedPosition`方法或ControlHasMoved属性。

  • 该字段是"私有"字段,唯一读取该字段的方法是为了检查其属性而未将对象本身暴露于外部代码,并且写入该字段的唯一方法将创建一个新对象,执行所有变异将会发生的事情,然后存储对该对象的引用。例如,可以通过使结构在私有字段中保存数组,并通过每次尝试写该数组的方式来创建一个带有数据的新数组,来设计一个行为类似于数组但具有值语义的"结构"。从旧的数组中,修改新的数组,并将修改后的数组存储到该字段。请注意,即使数组本身是可变类型,但将存储在该字段中的每个数组实例实际上都是不可变的,因为任何可能对其进行改动的代码都无法访问它。

    请注意,方案#1在泛型类型中很常见;例如,有一本字典的"值"是可变对象的身份是很常见的。枚举该字典将返回KeyValuePair实例,其Value字段保留该可变类型。

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


    这样做可能不建议这样做:请参阅http://msdn.microsoft.com/en-us/library/ms229017(VS.85).aspx

    Reference types are allocated on the heap, and memory management is
    handled by the garbage collector.

    Value types are allocated on the stack or inline and are deallocated
    when they go out of scope.

    In general, value types are cheaper to allocate and deallocate.
    However, if they are used in scenarios that require a significant
    amount of boxing and unboxing, they perform poorly as compared to
    reference types.


  • 推荐阅读