17

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

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

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 是否有值类型中的引用类型示例?我们什么时候应该这样做(如果有的话)?

4

3 回答 3

20

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

但是,是的,您不能保证在 a 中初始化引用类型字段,struct因为您不能定义无参数构造函数(也不能保证它永远被调用,如果您用 C# 以外的语言定义它)。

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

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

如果您不想传入Personto initialize的实例,则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.
}
于 2012-05-02T13:55:17.757 回答
3

对于包含类类型字段的结构,有两个主要有用的场景:

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

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

(**) 结构在某些情况下会表现得更好;类将在其他方面表现更好。一般来说,所谓的“不可变”结构会在预期表现更好的情况下被选择而不是类,并且它们的语义与类的语义不同的极端情况不会造成问题。

有些人喜欢假装结构就像类,但效率更高,并且不喜欢以利用结构不是类这一事实的方式使用结构。这样的人可能只会倾向于使用上面的场景(2)。场景 #1 对于可变结构非常有用,尤其是String对于本质上表现为值的类型。

于 2012-05-02T16:55:29.010 回答
0

我想补充 Marc 的答案,但我有太多话要说。

如果您查看 C# 规范,它会说 struct 构造函数:

使用 new 运算符调用结构构造函数,但这并不意味着正在分配内存。结构构造函数不是动态分配对象并返回对它的引用,而是简单地返回结构值本身(通常在堆栈上的临时位置),然后根据需要复制该值。

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

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

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

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

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

话虽如此,您在结构的构造函数中看到的错误可能是您有一个p与参数同名的公共字段,p并且没有引用结构的pas this.p,或者您缺少关键字struct

于 2012-05-02T14:10:04.900 回答