为什么这个C#赋值会抛出异常?

7
public class A
{
    private string _a_string;
    public string AString
    {
        get { return _a_string; }
        set {  _a_string = value; }
    }
}

public class B
{
    private string _b_string;
    private A _a;

    public A A
    {
        get { return _a; }
        set { _a = value; }
    }

    public string BString
    {
        get { return _b_string;  }
        set { _b_string = value; }
    }
}

这个不起作用:

    B _b = new B { A = { AString = "aString" }, BString = "bString" };

System.NullReferenceException : 对象引用未设置为对象的实例。

这个有效:

    B _b = new B { A = new A { AString = "aString" }, BString = "bString" };

在VS2010中,这两个都可以成功编译。

4个回答

6

这行代码

B _b = new B { A = { AString = "aString" }, BString = "bString" };

等同于

B _b = new B();
_b.A.AString = "aString"; // null reference here: _b.A == null
_b.BString = "bString";

我认为在这个表达方式中,情况已经很清楚了。

将其与有效的等价表达式进行比较:

B _b = new B();
_b.A = new A();
_b.A.AString = "aString"; // just fine now
_b.BString = "bString";

你可能是正确的,但我认为 A = { AString = "aString" } 应该可以自己创建 A 的实例。我想我必须查看 IL 才能真正找出答案... - Otávio Décio
@OtávioDécio:不会,因为编译器只是将“简写”语法转换为长语法。但说实话,如果你一个小时前问我,我也不会认为这个有问题的表达式是合法的C#。 - Jon
尽管我已经修复了我的代码,但我认为这种行为足以引起一些混乱。猜想从现在开始我将不得不更加正统地初始化对象。 - Otávio Décio
1
@OtávioDécio:我认为,如果不违反类的语义,最好在class B内部初始化字段支持B.A。这样,您就不必担心自己(或其他人)会陷入这个陷阱。 - Jon

4

除非你像第二个例子中那样明确实例化它,否则B内部不会有A的实例化。

更改为:

public class B
{
    private string _b_string;
    private A _a = new A();

    public A A
    {
        get { return _a; }
        set { _a = value; }
    }

    public string BString
    {
        get { return _b_string;  }
        set { _b_string = value; }
    }
}

使用第一个例子。

简而言之,如果没有 new A(),就没有 A,这是 NullReferenceException 的原因。


谢谢 - 我现在知道如何让它工作了,但我真的认为 A = { AString = "aString" } 已经足够赋值了。 - Otávio Décio

1
在第一种情况下,编译器会将代码有效地转换为。
B b = new B();
A a = b.A;
a.AString = "aString";
b.BString = "bString";

由于您从未将b.A分配给实例,因此会出现异常(具体来说,由于您从未分配b.A,所以a为null,因此a.AString将抛出NullReferenceException)。 事实上,没有形式为new A...的代码是一个巨大的线索,表明A的实例从未被创建。

您可以通过在B的构造函数中添加A = new A()或向B._a添加字段初始化器来解决此问题。

在第二种情况下,编译器将代码转换为

B b = new B();
A a = new A();
a.AString = "aString";    
b.A = a;
b.BString = "bString";

这是可以的,因为我们现在有一个A的实例。


谢谢 - 但是当我写 A = { AString = "aString" } 时,为什么编译器没有创建一个A的新实例呢? - Otávio Décio
1
因为你没有在任何地方使用 new A。除非你告诉它,否则它不会创建实例。你可以使用 new 关键字来告诉它创建实例。 - jason
因为在你的类或代码中没有明确实例化A。在你写A = { AString = "aString" }的时候,它并不存在。 - ChrisBint

0

你不能使用

A = { AString = "aString" }

因为你做的同样的原因。
B _b = new B { 

你必须声明B(和A)的实例才能使用它。

如果你写成

B = B{...

你会得到类似的错误


@Reed - 如果我不能做到也没关系,但如果是这样的话,为什么编译器会接受它呢? - Otávio Décio
因为在编译时你尚未尝试访问A,所以它会编译通过。编译器知道A是一个有效的类,但此时它无法确定它是否已被实例化,因为这可能发生在其他地方。 - ChrisBint
@Chris - 你说得对,但我感觉这是一个容易陷入的陷阱。 - Otávio Décio
1
同意,但是你活到老学到老,我相信现在你知道了原因后会更容易发现这类问题 :) - ChrisBint
@Reed = 顺便提一下,写成 B = B { ... 就像你最后一行那样是不被编译器允许的。 - Otávio Décio

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