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;
上面给出了valid==true
,因为没有调用默认arg的构造函数,并且使用标准默认值val = 0.0创建对象。
如果结构是一个类,行为是valid==false
,这是我所期望的。
我发现这种行为上的差异,特别是结构案例中的行为令人惊讶和不直观 - 发生了什么? stuct构造的默认arg服务是什么? 如果没用,为什么要编译?
更新:澄清这里的重点不在于行为是什么 - 而是为什么在没有警告的情况下进行编译并且行为不直观。即如果未应用默认的arg,因为在新的Test()情况下没有调用构造函数,那么为什么要编译它?
答案 0 :(得分:8)
在C#中(至少直到C#6 - see blog post),调用new Test()
等同于编写default(Test)
- 实际上没有调用构造函数,提供了默认值。
默认arg没有任何意义,发生的事情是它可能是编译器实现中疏忽的结果,因为可选参数只在C#4中添加:
翻译new Test()
意味着的代码可能不知道是否存在可选参数;
在深入评论之后,我注意到Mads Torgersen的以下宝石:
到目前为止,编译器实现确实已经优化了#34; '新T()'当T是结构时,本质上意味着默认(T)。这实际上是一个错误 - 它总是应该调用一个实际的无参数构造函数(如果有的话) - 它可能一直存在,因为它在IL中是允许的。
对于您的示例,这意味着new Test()
被编译器有效地替换为default(Test)
- 因此这是一个错误,将在下一版本的Visual Studio中修复。
换句话说,你有一个角落案例。这可能是查看Visual Studio下一版本中的行为方式的好时机,因为这种行为正在发生变化。
答案 1 :(得分:2)
对于所有值类型T
,new T()
和default(T)
都是等效的。它们不调用任何构造函数,它们只是将所有字段设置为零。这也是为什么C#不允许你编写无参数构造函数的原因:public Test() { Val = double.NaN; }
无法编译,因为没有办法使用该构造函数。
你找到了一个角落案件。您的构造函数看起来就像它将用于new T()
一样。由于您的类型仍然是值类型,因此不会使用它。由于您的构造函数可以被调用,因此不会发出错误。
答案 2 :(得分:2)
我发现这种行为差异,尤其是行为 结构案例令人惊讶和不直观 - 发生了什么?什么 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
的调用:
将指定地址的值类型的每个字段初始化为a null引用或适当原始类型的0。 与 Newobj 不同, initobj 不会调用构造函数方法。 Initobj 用于初始化值类型,而newobj用于 分配和初始化对象。
这意味着编译器只是忽略带有默认参数的构造函数,因为直到C#-6.0才禁止声明这样的构造函数。
@JonSkeet在对Does using "new" on a struct allocate it on the heap or stack?
的回答中深入探讨了这一点修改强>:
我实际上已经问过 Mads Torgerson 关于C#-6.0中无参数构造函数的新用法的问题,我认为这是相关的,他说:
@Yuval和其他人,关于结构上的无参数构造函数: 要意识到的是,在此之前和现在,构造函数都没有 必然在结构上运行。我们所做的只是增加了拥有的能力 无参数构造函数也无法保证运行。那里 没有合理的方法来保证已经存在的结构 初始化和无参数构造函数对此没有帮助。
无参数构造函数帮助你的东西是允许你拥有的 无参数构造函数。
我认为混淆的主要原因是允许'新S()' 意思是'默认(S)'。这是语言和我的历史错误 我非常希望能把它带走。我强烈劝阻任何人 在没有无参数的结构上使用'new S()' 构造即可。据我所知,这是因为默认(S) C#1.0中不存在语法,所以这只是用于的语法 获取结构的默认值。
到目前为止,编译器实现已经“优化”了 当T是结构时,'new T()'表示基本上默认(T)。那是 实际上是一个错误 - 它总是被称为实际的 无参数构造函数,如果有的话 - 可能有 一直以来,因为在IL中是允许的。我们正在解决这个问题 我们甚至会在通用情况下调用构造函数。
因此语义是干净的:新的S()是运行a的唯一方法 struct上的无参数构造函数,总是运行它 构造函数 - 甚至通过泛型。
答案 3 :(得分:1)
我怀疑这是因为c#中不允许使用没有参数的默认构造函数,因此当您调用不带参数的构造函数Test时,它只是像往常一样初始化它们。查看此帖子了解更多详情(不完全重复):Why can't I define a default constructor for a struct in .NET?
答案 4 :(得分:1)
因为结构不能有用户定义的无参数构造函数。
Test(double val = double.NaN)
看起来像,但它实际上编译为Test(double val)
,其中包含一些有关默认值的元数据。