C# 具有结构初始化和默认参数的非直观行为
上面给出了C# 具有结构初始化和默认参数的非直观行为,c#,.net,struct,default-constructor,default-arguments,C#,.net,Struct,Default Constructor,Default Arguments,上面给出了valid==true,因为不调用带有默认arg的构造函数,并且使用标准默认值val=0.0创建对象。 如果结构是一个类,那么行为就是valid==false,这就是我所期望的 我发现这种行为上的差异,特别是struct案例中的行为令人惊讶且不直观——到底发生了什么?stuct构造上的默认参数用于什么如果没有用,为什么让它编译? 更新:这里要澄清的重点不是行为是什么,而是为什么在没有警告的情况下编译并且行为不直观。也就是说,如果默认参数没有被应用,因为在new Test()的情况下构造
valid==true
,因为不调用带有默认arg的构造函数,并且使用标准默认值val=0.0创建对象。如果结构是一个类,那么行为就是
valid==false
,这就是我所期望的
我发现这种行为上的差异,特别是struct案例中的行为令人惊讶且不直观——到底发生了什么?stuct构造上的默认参数用于什么如果没有用,为什么让它编译?
更新:这里要澄清的重点不是行为是什么,而是为什么在没有警告的情况下编译并且行为不直观。也就是说,如果默认参数没有被应用,因为在new Test()的情况下构造函数没有被调用,那么为什么让它编译?我怀疑这是因为c中不允许使用没有参数的默认构造函数,所以当你调用没有参数的构造函数测试时,它只是像往常一样初始化它们。查看此帖子了解更多详细信息(不是完全重复):对于所有值类型
T
,new T()
和default(T)
是等效的。它们不调用任何构造函数,只是将所有字段设置为零。这也是为什么C#不允许您编写无参数构造函数的原因:public Test(){Val=double.NaN;}
不会编译,因为无法使用该构造函数
你找到了一个角落的箱子。您的构造函数看起来将用于newt()
。因为您的类型仍然是值类型,所以不使用它。由于可以调用构造函数,因此不会发出错误。在C#(至少在C#6-)中,调用new Test()
相当于编写default(Test)
-实际上没有调用构造函数,而是提供了默认值
默认的arg没有任何用途,所发生的情况是,它可能是编译器实现中的疏忽造成的,因为可选参数仅添加在C#4中:
- 检查可选参数是否与现有重载不冲突的代码不知道在结构的情况下与初始值设定项可能存在冲突李>
- 翻译
意思的代码可能不知道可选参数的存在newtest()
- 在深入研究评论之后,我注意到以下几点:
的确,到目前为止,编译器实现已经“优化”了“new T()”,当T是一个结构时,它实际上意味着默认值(T)。这实际上是一个bug——它总是应该调用一个实际的无参数构造函数(如果有),这可能一直存在,因为它在IL中是允许的
对于您的示例,这意味着
被编译器有效地替换为newtest()
——因此这是一个bug,将在下一版本的Visual Studio中修复default(Test)
- 在深入研究评论之后,我注意到以下几点:
的确,到目前为止,编译器实现已经“优化”了“new T()”,当T是一个结构时,它实际上意味着默认值(T)。这实际上是一个bug——它总是应该调用一个实际的无参数构造函数(如果有),这可能一直存在,因为它在IL中是允许的
对于您的示例,这意味着
换句话说,你有一个角落的情况。在Visual Studio的下一个版本中,这可能是一个很好的时机来了解它的行为,因为这种行为正在发生变化。因为结构不能有用户定义的无参数构造函数
Test(double-val=double.NaN)
看起来像一个,但实际上编译为Test(double-val)
,带有一些关于默认值的元数据
我发现这种行为上的差异,尤其是
结构案例令人惊讶且不直观-发生了什么?什么
stuct构造上的默认参数是否可用?如果没用,为什么
让它编译
它不起任何作用。发出的IL代码不会使用默认参数生成对构造函数的调用,但会调用default(Test)
。编译器发出警告说将不会调用构造函数(尽管这是一个实现细节),这似乎是完全合理的。我提出一个问题
如果我们查看生成的IL代码:
public struct Test
{
public double Val;
public Test(double val = double.NaN) { Val = val; }
public bool IsValid { get { return !double.IsNaN(Val); } }
}
Test myTest = new Test();
bool valid = myTest.IsValid;
我们将看到:
Test myTest = new Test();
bool valid = myTest.IsValid;
注意,在IL中进行的调用不是对构造函数的方法调用(看起来像:calltest..ctor
)
,它生成了一个调用:
将指定地址处的值类型的每个字段初始化为
空引用或相应基元类型的0。
与Newobj不同,initobj不调用构造函数方法。Initobj
用于初始化值类型,而newobj用于
分配和初始化对象
这意味着编译器只会忽略带有默认参数的构造函数,因为在C#-6.0之前,禁止声明这样的构造函数
@琼斯基特在回答这个问题时非常深入地阐述了这一点
编辑:
实际上,我问了Mads Torgerson一个关于C#-6.0中无参数构造函数的新用法的问题,我认为这是相关的,他说:
@Yuval和其他人,关于结构上的无参数构造函数:
要认识到的是,在以前和现在,构造函数都不需要
必须在结构上运行。我们所做的只是增加了拥有
也不能保证运行的无参数构造函数。那里
没有一种合理的方法可以保证结构已经
初始化,无参数构造函数对此没有帮助
无参数构造函数的帮助是允许
无参数构造函数
我认为造成混淆的主要原因是'news()'被允许
表示“违约”。这是语言中的一个历史错误,我
真希望我能把它拿走。我会极力劝阻任何人
在没有无参数的结构上使用“new S()”
构造函数。据我所知,这是因为默认值
C#1.0和s中不存在语法
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