18
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我所期望的。

我发现这种行为差异,尤其是 struct 案例中的行为令人惊讶且不直观——这是怎么回事?stuct 构造上的默认 arg 有什么作用? 如果它没用,为什么要编译?

更新:为了澄清这里的重点不是行为是什么 - 而是为什么它会在没有警告的情况下编译并且行为不直观。即,如果没有应用默认参数,因为在 new Test() 情况下没有调用构造函数,那么为什么要让它编译呢?

4

5 回答 5

10

在 C# 中(至少在 C# 6 之前 -请参阅博客文章),调用new Test()等同于编写default(Test)- 实际上没有调用构造函数,提供了默认值。

默认 arg 没有任何作用,发生的情况是它可能是编译器实现中的疏忽的结果,因为可选参数仅在 C# 4 中添加:

  • 检查可选参数是否与已经存在的重载冲突的代码不知道在结构的情况下可能与初始化程序发生冲突;
  • 翻译什么new Test()意思的代码可能不知道可选参数的存在;

    • 在深入研究评论后,我注意到了Mads Torgersen的以下宝石:

      确实,到目前为止,编译器实现已经“优化”了“new T()”,当 T 是一个结构时,它基本上意味着 default(T)。这实际上是一个错误 - 如果有一个实际的无参数构造函数,它总是应该调用 - 这可能一直存在,因为它在 IL 中是允许的。

      对于您的示例,这意味着它new Test()被编译器有效地替换为default(Test)- 所以这是一个错误,将在下一版本的 Visual Studio 中修复。

换句话说,你有一个角落案例。这可能是查看下一个版本的 Visual Studio 中的行为方式的好时机,因为这种行为正在发生变化。

于 2014-11-26T09:44:35.117 回答
2

我发现这种行为差异,尤其是 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() 是在结构上运行无参数构造函数的唯一方法,并且它始终运行该构造函数 - 即使通过泛型也是如此。

于 2014-11-26T09:53:07.507 回答
2

对于所有的值类型Tnew T()default(T)都是等价的。他们不调用任何构造函数,他们只是将所有字段设置为零。这也是 C# 不允许您编写无参数构造函数的原因:public Test() { Val = double.NaN; }不会编译,因为无法使用该构造函数。

你找到了一个角落案例。您的构造函数看起来将用于new T(). 由于您的类型仍然是值类型,因此未使用它。由于可以调用您的构造函数,因此不会发出错误。

于 2014-11-26T09:43:35.997 回答
1

因为结构不能有用户定义的无参数构造函数。

Test(double val = double.NaN) 看起来像一个,但它实际上是Test(double val)与一些关于默认值的元数据一起编译的。

于 2014-11-26T09:46:24.593 回答
1

我怀疑这是因为在 c# 中不允许使用没有参数的默认构造函数,所以当你调用没有参数的构造函数 Test 时,它只会像往常一样初始化它们。查看这篇文章了解更多详细信息(不完全重复):为什么我不能为 .NET 中的结构定义默认构造函数?

于 2014-11-26T09:42:23.627 回答