值类型何时包含引用类型

本文关键字:引用类型 何时包 类型 | 更新日期: 2023-09-27 18:32:51

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

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

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

值类型何时包含引用类型

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

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

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

(**(结构在某些情况下会表现得更好;类在其他情况下会表现得更好。 通常,所谓的"不可变"结构是在预期它们性能更好的情况下选择的,并且它们的语义与类的语义不同的极端情况预计不会造成问题。

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

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

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

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

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

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

除此之外,结构应该按值复制,因此:

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

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

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