struct QuaternionStruct
{
float x;
float y;
float z;
float w;
}
这是一个四元数的简单定义。
C# 目前的语法不允许为 struct 定义无参构造函数,凡是未显式调用带参版本的构造函数时,struct 对象初始化时总是简单的清零,这样就可以实现高效初始化(想象你有一个 struct 数组,如果每个 struct 元素需要调用构造函数,可能是非常大的开销)。但是,有时 struct 的零状态可能是没有意义的(比如上面的 Quaternion,通常我们会将 w 字段初始化为 1,代表默认旋转姿态),因为 C# 不允许为 struct 定义无参构造函数,我们不得不使用其他方式初始化这种 struct。
那么,如果允许为 struct 定义无参构造函数呢?
C# 中未显式初始化的成员字段会被初始化为默认值。对于引用类型成员字段,默认值是 null;对于值类型成员字段,默认值是零状态。其实两者本质上都是执行清零初始化,这在语法上是统一的。但是,如果需要显式初始化呢?
class QuaternionClass
{
float x;
float y;
float z;
float w;
public QuaternionClass()
{
x = 0;
y = 0;
z = 0;
w = 1;
}
}
class Program
{
QuaternionStruct valueType = new QuaternionStruct();
QuaternionClass referenceType = new QuaternionClass();
}
这里,Program 的值类型字段和引用类型字段都调用了无参构造函数执行初始化,但是结果却完全不同:因为引用类型的自定义无参构造函数将 w 初始化为 1,这样引用对象就不是零状态了;而值类型的初始化语法则令人困惑,因为值类型根本没有无参构造函数,仅仅是执行了清零初始化,这样一个已经初始化的值类型对象仍保持着没有意义的零状态!
也就是说,一个更统一、清晰、易用、易理解的语法,应当区别清零初始化与无参构造,将它们视为两种不同的过程。同时应当允许为 struct 定义无参构造函数。
这样可以带来哪些好处/变化?
首先是用于初始化的表达式语法可以统一了:
void DoSomething()
{
// 这两条语句都是执行清零
QuaternionStruct valueType1 = default;
QuaternionClass referenceType1 = default; // 或者 null
// 这两条语句都可以将对象初始化为有效状态(非零状态)
QuaternionStruct valueType2 = new QuaternionStruct();
QuaternionClass referenceType2 = new QuaternionClass();
// 这两条语句都创建了元素全是 default 值的数组
var valueTypeArray = new QuaternionStruct[100];
var referenceTypeArray = new QuaternionClass[100];
}
同时也意味着 struct 的成员字段应当允许使用“=”设置初始值了。
高效清零初始化仍然有效,还可以通过编译器优化将没有“实际价值”的自定义无参构造函数替换为高效清零。
还有就是如果定义了 struct 带参版本的构造函数而没有定义无参版本,理想设计应当是和 class 语法一致,即视为不存在无参构造函数;但考虑到兼容现有代码,凡是显式调用无参构造函数的代码暂处理为警告而非错误。
对于现有代码,因为之前没人写过 struct 的无参构造函数,所以凡是显式调用无参构造函数的代码与原语义是一致的。因此,允许为 struct 定义无参构造函数是向后兼容的。
(可能还有我没考虑到的地方,欢迎交流)