这是C# 4.0编译器的一个错误吗?

16

这段代码编译通过了,但我认为它应该无法编译通过。此外,当你运行它时,会出现一个 NullReferenceException 异常。缺少的代码是在 Bar 属性的初始化中添加 "new Bar"。

class Bar
{
    public string Name { get; set; }
}

class Foo
{
    public Bar Bar { get; set; }
}


class Program
{
    static void Main(string[] args)
    {
        var foo = new Foo
                      {
                          Bar = { Name = "Hello" }
                      };
    }
}

这是一个已知的 bug 吗?


3
你认为它为什么不能通过编译?我不会一开始就假设这是编译器的错误。 - Steve Townsend
因为这根本不可能起作用。 - Maxm007
2
@Maxm007:你怎么知道它永远不会工作?除非编译器有一种算法来确定何时出错,否则编译器无法提供错误。你认为应该是哪一组情况出现错误呢? - Eric Lippert
7个回答

41

为什么你认为它无法编译通过?这是嵌套对象初始化语法,客户代码有责任提供有效的初始化值。

根据文档:

C# spec 7.5.10.2 "Object initializers"

在等号后面指定一个对象初始化程序的成员初始化程序是一个嵌套的对象初始化程序,即嵌入对象的初始化。与将新值分配给字段或属性不同,嵌套对象初始化程序中的赋值被视为对字段或属性的成员的赋值。


1
哇,我不知道!非常感谢。 - Dan Abramov
问题描述得非常清楚。 - NotMe

16

不,这不是一个bug。

如果你想要它运行,你需要在Bar之前加上new(就像在初始化器之前为Foo做的一样),或者在Foo的构造函数中创建Bar对象。

对象初始化器本质上只是语法糖。

这个:

var foo = new Foo
            {
                Bar = { Name = "Hello" }
            };

与此完全相同:

var foo = new Foo();
foo.Bar.Name = "Hello"; 

3
在对象初始化器中,new是不必要的:
object-creation-expression:
    new   type   (   argument-list(opt)   )   object-or-collection-initializer(opt) 
    new   type   object-or-collection-initializer

object-or-collection-initializer:
    object-initializer
    collection-initializer

object-initializer:
    {   member-initializer-list(opt)   }
    {   member-initializer-list   ,   }

initializer-value:
    expression
    object-or-collection-initializer

最重要的是最后一个。它代表了您的property = value语法中的右侧。这意味着右侧可以是一个正常的C#表达式(带有new运算符)或者另一个初始化程序。在这种情况下,您只需要开放和关闭大括号。


是的,我明白新的东西不是必需的,但是 bar 的类型在哪里? - Maxm007
这是因为根据正在初始化的属性,已知类型而得出的推断。 - Kirk Woll
如果它推断出它是一个酒吧,然后假设它是新的酒吧,为什么会抛出nullrefexception异常。 - Maxm007
啊,明白了。如果你预先初始化bar,它就能工作。我不知道你可以为现有实例使用初始化器。 - Maxm007

2
如果您将代码更改为以下等效代码,您也将得到一个运行时错误NullReferenceException,而不是编译时错误/警告。
static void Main(string[] args) {
    Foo foo2 = new Foo();
    foo2.Bar.Name = "test";
}

效果是一样的,Bar从未被正确初始化。从编译器编写者的角度来看,在所有情况下确定Bar是否在使用之前正确初始化是极其困难的。

2
...
 Bar = { Name = "Hello"}
...

意思是:Foo.Bar.Name="Hello"

而不是:{Foo.Bar=new Bar(); Foo.Bar.Name="Hello";}

这段代码可以编译通过,且不会抛出任何异常,所以它并不是一个 bug,你只是在初始化一个不存在的对象:

class Bar
{
    public string Name;
}
class Foo
{
private Bar _bar = new Bar();
public Bar Bar
{
  get { return _bar; }
  set { _bar = value; }
}
}
class Program
{
 static void Main(string[] args)
 {
  Foo foo = new Foo
  {
   Bar = { Name = "Hello"}
  };
 }
}

1

BarFoo 的一个属性,因此它允许您在编译时访问它并分配一个名称属性,但在运行时它会检查有效的 Bar 实例是否存在,如果不存在则会抛出空引用异常,这将是任何 C# 版本的情况。


0
我创建了一个工作示例
很简单,只需添加一个"new Bar()"就可以正常工作。
class Bar
{
    public string Name { get; set; }
}

class Foo
{
    public Bar Bar { get; set; }
}


class Program
{
    static void Main(string[] args)
    {
        var foo = new Foo
                      {
                          Bar = new Bar() { Name = "Hello" }
                      };

        Console.WriteLine(foo.Bar.Name);
        Console.ReadLine();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接