C# 10 可以为结构声明无参数构造函数和字段初始值设定项。根据语言参考,当您声明字段初始值设定项但没有显式无参数构造函数时,会发生以下情况:
如果不显式声明无参数构造函数,结构类型会提供无参数构造函数,其行为如下:
如果结构类型具有显式实例构造函数或没有字段初始值设定项,则隐式无参数构造函数会生成结构类型的默认值,无论字段初始值设定项如何(...)。
如果结构类型没有显式实例构造函数并且具有字段初始值设定项,则编译器会合成一个公共无参数构造函数来执行指定的字段初始化(...)。
以下代码输出
1
:
struct S {
public int X = 1;
}
class Program {
static void Main() { System.Console.WriteLine(new S().X); }
}
以下代码输出
0
:
struct S {
public int X = 1;
public S(int x) { X = x; }
}
class Program {
static void Main() { System.Console.WriteLine(new S().X); }
}
这完全符合上面引用的规范,但我发现这不合逻辑,所以我的问题是:为什么?为什么隐式无参数构造函数根据是否存在其他实例构造函数而具有不同的行为?为什么它不能总是考虑字段初始值设定项,即使有其他实例构造函数?
struct
上的隐式无参数构造函数将仅使用其基本 default
值,并且不会遵循您定义的默认值。 IMO,.NET 分析器应该做得更好,让您知道您编写的内容何时不起作用。
例如,在 .NET 8.0.402 中,以下内容将在编译时没有错误或警告,但实际上不会使用预期的
new()
和 10
值实例化 "my string"
。
// IMPLICIT parameterless constructor
public struct MyStruct
{
public int Param1 { get; }
public string Param2 { get; }
// confusing: these user-defined defaults are
// ignored when you try to instantiate with `new()`
public MyStruct(int param1 = 10, string param2 = "my string")
{
Param1 = param1;
Param2 = param2;
}
}
在这种情况下,
MyStruct foo = new(42);
和MyStruct foo = new(10,"my string");
仍将按预期工作,但您的无参数实例化MyStruct foo = new();
不会。
...
[TestMethod]
public void MyStruct_ImplicitCtor()
{
MyStruct myStruct = new();
Console.WriteLine($"P1: {myStruct.Param1} | P2: {myStruct.Param2}");
// outputs type default only: P1: 0 | P2: ""
// failing tests
Assert.AreEqual(10, myStruct.Param1);
Assert.AreEqual("my string", myStruct.Param2);
}
对于那些希望
new()
按预期工作的人,您必须创建一个显式定义的无参数构造函数。
// EXPLICIT parameterless constructor
public struct MyStruct
{
public int Param1 { get; }
public string Param2 { get; }
public MyStruct(int param1, string param2 = "my string")
{
Param1 = param1;
Param2 = param2;
}
// ADD THIS
// explicit parameterless constructor
public MyStruct() : this(param1: 10, param2: "my string") { }
}
使用显式定义的无参数构造函数,现在可以工作了:
...
[TestMethod]
public void MyStruct_ExplicitCtor()
{
MyStruct myStruct = new();
Console.WriteLine($"P1: {myStruct.Param1} | P2: {myStruct.Param2}");
// outputs your defaults: P1: 10 | P2: "my string"
// passing tests
Assert.AreEqual(10, myStruct.Param1);
Assert.AreEqual("my string", myStruct.Param2);
}