值类型何时包含引用类型
本文关键字:引用类型 何时包 类型 | 更新日期: 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.
}
对于包含类类型字段的结构,有两种主要的有用方案:
- 该结构包含对不可变对象的可能可变引用("字符串"是迄今为止最常见的(。 对不可变对象的引用将表现为可为 null 的值类型和普通值类型之间的交叉;它没有前者的"Value"和"HasValue"属性,但它将具有null作为可能的(和默认(值。 请注意,如果通过属性访问字段,则当字段为 null 时,该属性可能会返回非 null 默认值,但不应修改字段本身。
- 该结构包含对可能可变对象的"不可变"引用,并用于包装对象或其内容。 "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
。