当您处理结构时,C# 想要确保您已初始化结构的所有字段。
一种选择是提供一个构造函数,它首先调用默认构造函数,然后再进行自定义初始化:
public S(int x) : this()
{
/* custom initialization logic */
}
另一种选择是提供一个构造函数,该构造函数可证明在自己的主体中初始化结构的所有字段,在这种情况下,您不需要链接默认构造函数:
public S(int x)
{
this.X = x;
/* custom initialization logic */
}
另一种选择是跳过调用构造函数,而是在实际使用之前手动初始化结构:
S s;
s.X = 5;
在上述选项中,只有第一个执行对默认构造函数的逻辑调用,而其他两个跳过调用。
坏消息是,在幕后,出于安全原因,CLR 将始终为您提供零初始化内存,这几乎完全等同于调用 C# 默认构造函数。你可以这样观察:
S s;
S* ptr = &s;
int x = (*ptr).X;
Console.WriteLine(x); // will print 0
好消息是,在大多数情况下,这无关紧要。 C# 默认构造函数并不是 CLR 意义上的真正构造函数。任何T : struct 的表达式new T() 等价于default(T),这只是表示获取T 类型结构的零初始化实例的一种方式。这也是为什么您不能将其设为私有或向其添加自定义逻辑的原因。
因此,C# 默认结构构造函数不能有任何逻辑外部可观察的副作用。这给我们带来了一个问题:您要避免什么样的影响?
请记住,如果您的问题是您的结构对于全零位模式无效,并且您担心某些客户端代码可能会尝试这样使用它,那么有一种解决方法:
struct S
{
private bool _isValid;
/* rest of your struct */
}
任何零初始化的结构都不会设置其有效性标志,只要在结构上执行任何操作(您可以控制),您就可以使用它来引发异常。
毕竟,如果出于任何原因您仍然需要一个结构实例,该实例以某种方式跳过了调用任何构造函数(无论是真实的还是表面上的),您必须自己为该结构分配一些内存:
S* ptr = (S*) Marshal.AllocHGlobal(Marshal.SizeOf<S>());
您现在可以使用 (*ptr).X 或 ptr->X 形式的表达式直接读取和写入该结构。