我发现这种行为差异,尤其是 struct 案例中的行为令人惊讶且不直观——这是怎么回事?stuct 构造上的默认 arg 有什么作用?如果它没用,为什么要编译?
它没有任何作用。发出的 IL 代码不会使用默认参数生成对构造函数的调用,但会调用default(Test)
. 编译器会发出警告说构造函数将不会被调用,这似乎是完全合理的(尽管这是一个实现细节)。我在http://connect.microsoft.com上提出问题
如果我们查看生成的 IL 代码:
Test myTest = new Test();
bool valid = myTest.IsValid;
走着瞧:
IL_0000: ldloca.s 00 // myTest
IL_0002: initobj UserQuery.Test // default(Test);
IL_0008: ldloca.s 00 // myTest
IL_000A: call UserQuery+Test.get_IsValid
请注意,在 IL 中进行的调用不是对构造函数的方法调用(看起来像:)call Test..ctor
,它生成了对initobj
:
将指定地址处的值类型的每个字段初始化为空引用或相应原始类型的 0。与Newobj不同,initobj不调用构造函数方法。Initobj 用于初始化值类型,而 newobj 用于分配和初始化对象。
这意味着编译器只是忽略了具有默认参数的构造函数,因为在 C#-6.0 之前,禁止声明这样的构造函数。
@JonSkeet 在他对在结构上使用“新”是否在堆或堆栈上分配它的回答中对此进行了深入探讨?
编辑:
我实际上问了Mads Torgerson一个关于 C#-6.0 中无参数构造函数的新用法的问题,我认为这是相关的,他说:
@Yuval 和其他人,关于结构上的无参数构造函数:要意识到的是,之前和现在,构造函数不一定在结构上运行。我们所做的只是增加了一个无参数构造函数的能力,该构造函数也不能保证运行。没有合理的方法可以保证已初始化的结构,无参数构造函数对此无济于事。
无参数构造函数的帮助是允许您拥有一个无参数构造函数。
我认为混淆的一个主要来源是允许“new S()”表示“default(S)”。这是语言中的一个历史错误,我非常希望我能把它拿走。我强烈劝阻任何人不要在没有无参数构造函数的结构上使用“new S()”。据我所知,这是因为默认(S)语法在 C# 1.0 中不存在,所以这只是用于获取结构默认值的语法。
确实,到目前为止,编译器实现已经“优化”了“new T()”,当 T 是一个结构时,它基本上意味着 default(T)。这实际上是一个错误 - 如果有一个实际的无参数构造函数,它总是应该调用 - 这可能一直存在,因为它在 IL 中是允许的。我们正在修复这个问题,这样即使在通用情况下我们也会调用构造函数。
因此语义是干净的: new S() 是在结构上运行无参数构造函数的唯一方法,并且它始终运行该构造函数 - 即使通过泛型也是如此。