【问题标题】:Why does this wrong object initialisation with curly braces even compile? [duplicate]为什么用花括号进行的错误对象初始化甚至可以编译? [复制]
【发布时间】:2018-03-05 22:46:14
【问题描述】:

在为 WPF/MVVM 项目的集合创建一些虚拟数据时,我生成了以下错误代码,可以正常编译,但在运行时抛出异常。

有一个数据对象的嵌套结构,我错误地只用花括号对其进行了实例化(看起来编写 JavaScript 确实会对大脑造成永久性损害)。

using System.Collections.ObjectModel;

namespace testapp
{
    class Program
    {
        static void Main(string[] args)
        {
            var collection = new ObservableCollection<TopLevelDataObject>();
            collection.Add(new TopLevelDataObject{Nested = {Bar = 5}});         // NullReferenceException
        }

        class TopLevelDataObject
        {
            public NestedDataObject Nested { get; set; }
            public string Foo { get; set; }
        }

        class NestedDataObject
        {
            public double Bar { get; set; }
        }
    }
}

为什么会编译?

如果我创建一个匿名类型,例如 Nested = new {Bar = 5},我会在编译期间收到错误消息(因此失败):

Cannot implicitly convert type '<anonymous type: int Bar>' to 'testapp.Program.NestedDataObject'

为什么我在省略new 运算符时没有收到这样的错误?

它甚至为我提供了该属性的代码提示:

我的猜测是{Bar = 5} 只是一个代码块,它本身就是一个有效的东西。 但是为什么将代码块分配给任何东西(在本例中为 Nested 属性)是有效的?

【问题讨论】:

    标签: c# curly-braces object-initializers


    【解决方案1】:

    为什么会编译?

    因为在编译该代码时,它被编译为一组赋值操作。不一定都是您创建的新实例。

    如果您从构造函数构造Nested 的新实例,则可以为Nested.Bar 赋值。

    public NestedDataObject Nested { get; set; } 改成这个,看看它是如何工作的:

    public NestedDataObject Nested { get; } = new NestedDataObject();
    

    (请注意,在上面的代码中,您永远不能在构造函数之外为Nested 赋值!)

    【讨论】:

      【解决方案2】:

      为什么会编译?

      因为var x = new SomeType{Property = value}; 等同于:

      var x = new SomeType();
      x.Property = value;
      

      事实上,我们甚至可以在() 中留下var x = new SomeType(){Property = value}; 甚至var x = new SomeType(argument){Property = value};,将参数传递给构造函数并设置值。

      因此,您可以看到始终有一个构造函数被调用,如果您省略了使其显式的括号,则它始终是空值(无参数)构造函数。

      同时,没有显式构造函数的类型总是有一个公共的空值构造函数(“默认构造函数”)。

      因此new TopLevelDataObject{Nested = {Bar = 5}} 等同于:

      var temp = new TopLevelDataObject();
      temp.Nested.Bar = 5; // NRE on temp.Nested
      

      因为TopLevelDataObject 可能有一个构造函数来设置`Nestedt 然后你的代码可以工作,所以它应该编译。当然,因为它没有有这样的构造函数,所以它不起作用。

      (请注意,初始化器与匿名类型的操作并不完全相同,在这种情况下,它会被重写以调用隐藏的构造函数,因此即使初始化器不能与只读属性一起使用,也允许属性是只读的非匿名类型。语法允许它们看起来相同,因此很容易理解为相似但结果不一样)。

      【讨论】:

      • 如果问题在 20 分钟前关闭,怎么可能在 15 分钟前发布答案?
      • @Evk 我也很好奇。
      • @Evk 有一个宽限期使这成为可能,有关更多信息,请查看此元问题:meta.stackoverflow.com/q/252711/8958148
      猜你喜欢
      • 2018-09-13
      • 2012-08-04
      • 2020-10-20
      • 2021-07-29
      • 1970-01-01
      • 1970-01-01
      • 2021-04-02
      • 2023-03-05
      • 1970-01-01
      相关资源
      最近更新 更多