值类型何时包含引用类型?

时间:2012-05-02 13:52:05

标签: c# value-type reference-type

我理解在参考类型上使用值类型的决定应该基于语义,而不是性能。我不明白为什么值类型可以合法地包含引用类型成员?这有几个原因:

首先,我们不应该构建一个需要构造函数的结构。

public struct MyStruct
{
    public Person p;
    // public Person p = new Person(); // error: cannot have instance field initializers in structs

    MyStruct(Person p)
    {
        p = new Person();
    }
}

其次,由于值类型语义:

MyStruct someVariable;
someVariable.p.Age = 2; // NullReferenceException

编译器不允许我在声明中初始化Person。我必须将它移到构造函数,依赖调用者,或期望NullReferenceException。这些情况都不是理想的。

.NET Framework是否在值类型中有任何引用类型的示例?我们什么时候应该这样做(如果有的话)?

3 个答案:

答案 0 :(得分:20)

值类型的实例永远不会包含引用类型的实例。引用类型对象位于托管堆上的某个位置,值类型对象可能包含引用对象。这样的参考具有固定的大小。这样做很常见 - 例如每次在结构中使用字符串时。

但是,是的,你无法保证struct中引用类型字段的初始化,因为你无法定义无参数构造函数(如果你用其他语言定义它也不能保证它被调用)比C#)。

你说你应该“不构建struct来要求构造函数”。我说不然。由于值类型几乎总是是不可变的,因此必须使用构造函数(很可能通过工厂到私有构造函数)。否则它将永远不会有任何有趣的内容。

使用构造函数。构造函数很好。

如果您不想传入Person的实例来初始化p,则可以通过属性使用延迟初始化。 (因为显然公共领域p仅用于演示,对吧?对吗?)

public struct MyStruct
{
    public MyStruct(Person p)
    {
        this.p = p;
    }

    private Person p;

    public Person Person
    {
        get
        {
            if (p == null)
            {
                p = new Person(…); // see comment below about struct immutability
            }
            return p;
        }
    }

    // ^ in most other cases, this would be a typical use case for Lazy<T>;
    //   but due to structs' default constructor, we *always* need the null check.
}

答案 1 :(得分:3)

对于包含类类型字段的结构,有两种主要的有用方案:

  1. struct保存了对不可变对象的可能可变引用(`String`是目前最常见的)。对不可变对象的引用将表现为可空值类型和普通值类型之间的交叉;它没有前者的“Value”和“HasValue”属性,但它将作为可能(和默认)值的null。请注意,如果通过属性访问该字段,则该字段为null时该属性可能会返回非null默认值,但不应修改该字段本身。
  2. struct保存对可能可变对象的“不可变”引用,用于包装对象或其内容。 `List.Enumerator`可能是使用此模式的最常见结构。让struct字段假装是不可变的是一个狡猾的构造(*),但在某些情况下它可以很好地工作。在应用此模式的大多数情况下,结构的行为基本上类似于类的行为,但性能会更好(**)。

(*)语句structVar = new structType(whatever);将创建structType的新实例,将其传递给构造函数,然后通过复制所有公共和私有字段来变异structVar新实例进入structVar ;完成后,新实例将被丢弃。因此,所有结构域都是可变的,即使它们“假装”不是这样;假装它们是不可变的,除非知道structVar = new structType(whatever);实际实现的方式永远不会造成问题,否则它可能会变得狡猾。

(**)在某些情况下,结构会表现得更好;课程在其他课程中表现更好。通常,在期望它们表现更好的情况下,选择所谓的“不可变”结构,而在它们的语义与类的语义不同的极端情况下,预计不会产生问题。

有些人喜欢假装结构类似于类,但更有效率,并且不喜欢使用结构来利用它们不是类的事实。这些人可能只倾向于使用上面的情景(2)。场景#1对于可变结构非常有用,特别是像String这样的类型,它们基本上表现为值。

答案 2 :(得分:0)

我想补充一下Marc的回答,但我有太多话要说评论。

如果你看一下C#规范,它会说结构构造函数:

  

使用new运算符调用struct构造函数,但确实如此   并不意味着正在分配内存。而不是动态的   分配一个对象并返回一个对它的引用,一个结构   构造函数只返回结构值本身(通常在一个   堆栈上的临时位置),然后将该值复制为   必要的。

(您可以在中找到规格的副本 C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC#\Specifications\1033

因此,结构构造函数本质上与类构造函数不同。

除此之外,结构预计会按值复制,因此:

  

对于结构体,每个变量都有自己的数据副本,并且   对一方的操作不可能影响另一方。

每当我在结构中看到引用类型时,它就是一个字符串。这有效,因为字符串是不可变的。我猜你的Person对象不是不可变的,并且可能会引入非常奇怪和严重的错误,因为它与结构的预期行为有所不同。

话虽如此,您使用结构的构造函数看到的错误可能是您的公共字段p与您的参数p具有相同的名称而未提及结构pthis.p,或者您错过了关键字struct