如您所知,不允许对列表使用数组初始化语法。它将给出编译时错误。例子:
1 2 3
| List<int> test = { 1, 2, 3}
// At compilation the following error is shown:
// Can only use array initializer expressions to assign to array types. |
但是今天我做了以下工作(非常简单):
1 2 3 4 5 6 7 8 9
| class Test
{
public List <int> Field ;
}
List <Test > list = new List <Test >
{
new Test { Field = { 1, 2, 3 } }
}; |
上面的代码编译得很好,但是当运行时,它会给出一个"object references is not set to a object"运行时错误。
我希望代码给出一个编译时错误。我要问你的是:为什么不这样做呢?有没有什么好的理由说明这种情况何时能正常运行?
这已经使用.NET 3.5,包括.NET和Mono编译器进行了测试。
干杯。
- 更简单的示例Test test= new Test { Field = { 1, 2, 3 }};具有相同的行为。
- "对象引用没有设置为对象"是非常合乎逻辑的,因为列表字段没有初始化。将测试主体更改为public listfield=new list();使此运行也没有任何问题。
- 拿一个反射器,看看编译器是如何处理这个问题的,你会理解的。还可以查看var stuff=new list()4,5是如何处理的。
我认为这是一种设计行为。Test = { 1, 2, 3 }被编译为调用存储在Test字段中的列表的Add方法的代码。
你得到NullReferenceException的原因是Test是null。如果将Test字段初始化为新列表,则代码将工作:
2
这是非常合乎逻辑的——如果编写new List { ... },那么它将创建一个新的列表实例。如果不添加对象构造,它将使用现有实例(或null)。据我所见,C规范不包含任何与此场景匹配的显式转换规则,但它给出了一个示例(参见第7.6.10.3节):
可以创建和初始化List,如下所示:
1 2 3 4 5 6 7 8 9 10
| var contacts = new List <Contact > {
new Contact {
Name ="Chris Smith",
PhoneNumbers = {"206-555-0101", "425-882-8080" }
},
new Contact {
Name ="Bob Harris",
PhoneNumbers = {"650-555-0199" }
}
}; |
它的效果和
4
其中,__c1和__c2是临时变量,在其他情况下是不可见和不可访问的。
- 有点相关:stackoverflow.com/questions/459652/&hellip;
- 谢谢,回答得很好。根据这种描述,行为是合乎逻辑的。然而,对于我来说,仍然不符合逻辑的是允许使用语法来添加项,这样我会读到"这个字段等于那个",但事实上,如果在对象构造时添加了任何项,则列表中可能包含的项比我指定的项更多。我宁愿有编译错误;-)
- 是的,有点不一致。使用这个示例中的逻辑,应该可以编写var l = new List(); l = { 1, 2, 3 };,但这不起作用!
0
由于您的期望与规范和实现都相反,所以您的期望将无法实现。
Why doesn't it fail at compile time?
因为本规范在第7.6.10.2节中特别说明了这是合法的,为了您的方便,我在此报价:
A member initializer that specifies a collection initializer after the equals sign is an initialization of an embedded collection. Instead of assigning a new collection to the field or property, the elements given in the initializer are added to the collection referenced by the field or property.
when would such code run correctly?
正如规范所说,初始值设定项中给定的元素被添加到属性引用的集合中。属性未引用集合;它为空。因此,在运行时,它会给出一个空引用异常。必须有人初始化列表。我建议更改"test"类,以便其构造函数初始化列表。
What scenario motivates this feature?
LINQ查询需要表达式,而不是语句。在新创建的列表中向新创建的集合添加成员需要调用"添加"。由于"add"是void返回,因此对它的调用只能出现在表达式语句中。此功能允许您创建一个新集合(使用"new")并填充它,或者填充一个现有集合(不使用"new"),其中该集合是作为LINQ查询结果创建的对象的成员。
- 谢谢你对我的问题有一个非常直接的回答。正如我在其他答案上所评论的那样,在我们希望能够做什么的背景下,它的行为是合乎逻辑的,尽管它的语法并不十分合乎逻辑,正如人们所期望的那样,=操作可以做一个精确的分配。你的LINQ动机促成了这一点,我明白他们为什么决定这样设计它。
- 将它添加到msdn.microsoft.com/en-us/library/bb384062.aspx上的msdn文档中是不错的选择。它只显示集合/对象初始值设定项的其他变体。
- @Mika代码中=的含义与正常集合初始值设定项上的=相匹配。您的代码的一个不寻常之处是,其中没有new List。
- @米卡:看看这个问题,了解更多关于这个语法的思考。stackoverflow.com/questions/4773889/&hellip;
- @codeinchao的不寻常之处在于,在这个上下文中,=意味着"为我附加这个"。是的,它是匹配的,因为在这两种情况下它都是以相同的方式完成的。
- @埃里克·利珀特谢谢你的链接,这是一个有趣的阅读:—)
此代码:
1
| Test t = new Test { Field = { 1, 2, 3 } }; |
翻译如下:
1 2 3 4
| Test t = new Test ();
t .Field.Add(1);
t .Field.Add(2);
t .Field.Add(3); |
由于Field是null,所以得到NullReferenceException。
这称为集合初始值设定项,如果执行以下操作,它将在初始示例中工作:
1
| List <int> test = new List <int> { 1, 2, 3 }; |
为了能够使用这种语法,您确实需要更新一些内容,即集合初始值设定项只能出现在对象创建表达式的上下文中。在C规范第7.6.10.1节中,这是对象创建表达式的语法:
1 2 3 4 5 6
| object -creation -expression :
new type ( argument -list ? ) object -or -collection -initializer ?
new type object -or -collection -initializer
object -or -collection -initializer :
object -initializer
collection -initializer |
所以这一切都是从一个new表达式开始的。在表达式内部,可以使用不带new的集合初始值设定项(第7.6.10.2节):
1 2 3 4 5 6 7 8 9 10 11
| object-initializer:
{ member-initializer-list? }
{ member-initializer-list , }
member-initializer-list:
member-initializer
member-initializer-list , member-initializer
member-initializer:
identifier = initializer-value
initializer-value:
expression
object-or-collection-initializer // here it recurses |
现在,您真正缺少的是某种列表文字,这将非常方便。我在这里提出了一个这样的字面上的易读词。
- 是的,没错。原始代码在语法上是正确的,但实际上没有将这些项放入的列表。
- 最后一个例子是众所周知的。但我不知道在没有new MyCollection的对象初始值设定项中可以使用集合初始值设定项。你链接的msdn文章也没有提到这一点。
- 又一个好答案。我猜他们必须这样做,以允许添加项而不重写可能已添加到初始对象的构造函数中的值。这有点含糊不清,因为您希望=操作的结果值与右侧赋值完全匹配,但在这种情况下,它只添加一组值。
- @codeinchaos:你必须看看C语法才能看到它。我已经适当地更新了。
1
| var test = (new [] { 1, 2, 3}).ToList(); |
- 他不是在问如何使它工作,而是在问原始程序为什么还要编译。
- 它编译是因为它是有效的C,它在运行时失败是因为它没有实例化列表对象。
- @伊万诺夫:这是一个多么奇妙的解释。
- 接下来我们会问为什么除法是零编译的,为什么C编译器没有捕捉到这一点,对吗?
- 伊万诺夫,你是个讽刺的人,但你是对的。+ 1
- @伊万诺夫:你可能想试着在回答中而不是在评论中回答问题。当然,其他人现在回答得更好了。仅供参考,C编译器会捕获基本的被零除错误。例如int i = 1 / 0;不编译。
- @al-kepp:在我看来,答案的根源不是他没有实例化列表对象,而是编译器把初始化器中的list=array类型的东西变成了一系列单独的加法,而不仅仅是把它当作一个单独的赋值。如果这对你来说是显而易见的,那就太好了,但从反对这个问题的人数来看,显然对所有人来说都不是显而易见的。
- @K伊万诺夫:当你写A=1/0时,你会得到一个编译错误;当你写A=1/B时,每个人都可以猜测你正在分配一个新的值,这个值将在运行时计算。当有人写a=x,y时,你必须是专家才能发现这不是一个任务。
原因是第二个示例是成员列表初始化器,System.Linq.Expressions中的MemberListBinding表达式对此提供了深入的了解-有关详细信息,请参阅我对另一个问题的回答:MemberBinding Linq表达式的一些示例是什么?
这种类型的初始化器要求列表已经初始化,以便可以向其中添加您提供的序列。
因此,从语法上讲,代码完全没有问题,NullReferenceException是一个运行时错误,它是由于列表没有实际创建而导致的。由new作为列表的默认构造函数,或代码体中的内联new将解决运行时错误。
至于为什么这一行代码和第一行代码之间存在差异——在您的示例中,这是不允许的,因为这种类型的表达式不能位于赋值的右侧,因为实际上没有创建任何内容,它只是Add的简写。
- 我看了一下链接。有助于更深入地了解事情的实际情况。谢谢!至于我的例子,我想现在已经很清楚了,我得到了所有的答案:)
将代码更改为:
1 2 3 4
| class Test
{
public List <int> Field = new List <int>();
} |
原因是,必须先显式创建集合对象,然后才能向其放置项。
- 他不是在问如何使它工作,而是在问原始程序为什么还要编译。
- 它编译是因为它是正确的。你还想听什么?
- 你可以解释为什么操作的代码会编译。
- @安东尼·佩格拉姆-他说得对-使用的语法要求首先初始化列表,因为它是成员成员初始化器。