【问题标题】:List<int> test = {1, 2, 3} - is it a feature or a bug?List<int> test = {1, 2, 3} - 是功能还是错误?
【发布时间】:2011-06-21 10:07:03
【问题描述】:

如您所知,列表中不允许使用数组初始化语法。它会给出一个编译时错误。示例:

List<int> test = { 1, 2, 3} 
// At compilation the following error is shown:
// Can only use array initializer expressions to assign to array types. 

但是今天我做了以下(非常简化):

class Test
{
     public List<int> Field;
}

List<Test> list = new List<Test>
{
    new Test { Field = { 1, 2, 3 } }
};

上面的代码编译得很好,但是运行时会给出“对象引用未设置为对象”的运行时错误。

我希望该代码会产生编译时错误。我对您的问题是:为什么不这样做,是否有充分的理由说明这种情况何时会正确运行?

这已使用 .NET 3.5 进行了测试,包括 .Net 和 Mono 编译器。

干杯。

【问题讨论】:

  • 更简单的例子Test test= new Test { Field = { 1, 2, 3 }};具有相同的行为。
  • “对象引用未设置为对象”非常合乎逻辑,因为您的列表字段未初始化。将测试主体更改为 public List Field = new List();使这个运行也没有任何问题。
  • 拿个反射器,看看编译器是怎么处理的,你就明白了。还要看看 var stuff = new List() {4,5};被处理

标签: c# compiler-errors


【解决方案1】:

我认为这是一种设计行为。 Test = { 1, 2, 3 } 被编译成调用Add 存储在Test 字段中的列表的方法的代码。

您获得NullReferenceException 的原因是Testnull。如果将Test 字段初始化为新列表,则代码将起作用:

class Test {    
  public List<int> Field = new List<int>(); 
}  

// Calls 'Add' method three times to add items to 'Field' list
var t = new Test { Field = { 1, 2, 3 } };

这很合乎逻辑——如果你写new List&lt;int&gt; { ... },那么它会创建一个新的列表实例。如果不添加对象构造,它将使用现有实例(或null)。据我所知,C# 规范不包含任何与这种情况相匹配的显式翻译规则,但它提供了一个示例(参见 第 7.6.10.3 节):

List&lt;Contact&gt; 可以按如下方式创建和初始化:

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" }
    }
};

作用相同

var contacts = new List<Contact>();
Contact __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
contacts.Add(__c1);
Contact __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
contacts.Add(__c2);

其中__c1__c2 是临时变量,否则它们是不可见且不可访问的。

【讨论】:

  • 谢谢,很好的回答。有了这个描述,行为是合乎逻辑的。然而,对我来说仍然不合逻辑的是允许 = {} 语法来添加项目,这样我会读到“这个字段等于那个”,但实际上列表可能包含比我指定的项目更多的项目(如果有的话)在对象构造上。我宁愿有一个编译错误;-)
  • 是的,有一点不一致。使用此示例中的逻辑,应该可以编写 var l = new List&lt;int&gt;(); l = { 1, 2, 3 }; - 但那行不通!
【解决方案2】:

我希望该代码会产生编译时错误。

由于您的期望与规范和实现都背道而驰,因此您的期望将无法实现。

为什么编译时不会失败?

因为规范在第 7.6.10.2 节中明确指出这是合法的,为了方便起见,我在此引用:


在等号之后指定集合初始值设定项的成员初始值设定项是嵌入式集合的初始化。初始化器中给出的元素被添加到字段或属性引用的集合中,而不是为字段或属性分配新集合。


这样的代码什么时候才能正确运行?

正如规范所说,初始化器中给出的元素被添加到属性引用的集合中。该属性不引用集合;它是空的。因此在运行时它会给出一个空引用异常。有人必须初始化列表。我建议更改“Test”类,以便其构造函数初始化列表。

什么场景激发了这个功能?

LINQ 查询需要表达式,而不是语句。将成员添加到新创建列表中的新创建集合需要调用“添加”。由于“添加”是返回无效的,因此对它的调用只能出现在表达式语句中。此功能允许您创建一个新集合(带有“new”)并填充它,或者填充现有集合(不带“new”),其中该集合是您正在创建的对象的成员,该对象是 LINQ 的结果查询。

【讨论】:

  • 感谢您非常直接地回答我的问题。正如我对其他答案的评论一样,它在上下文中的行为对于我们希望能够做的事情是合乎逻辑的,尽管它的语法不是很合乎逻辑,因为人们期望 = 操作来做一个精确的分配.您的 LINQ 动机促成了这一点,我明白他们为什么决定这样设计它。
  • 将它添加到msdn.microsoft.com/en-us/library/bb384062.aspx 的 MSDN 文档会很好。它仅显示集合/对象初始化器的其他变体。
  • @Mika 代码中= 的含义与普通集合初始值设定项上的= 匹配。您的代码的不同寻常之处在于它没有new List&lt;T&gt;
  • @Mika:有关此语法的更多思考,请参阅此问题。 stackoverflow.com/questions/4773889/…
  • @CodeInChaos 不寻常的是,= 在这种情况下意味着“为我附加这个”。是的,它匹配,因为在两种情况下都以相同的方式完成。
【解决方案3】:

这段代码:

Test t = new Test { Field = { 1, 2, 3 } };

翻译成这样:

Test t = new Test();
t.Field.Add(1);
t.Field.Add(2);
t.Field.Add(3);

因为Fieldnull,所以你得到NullReferenceException

这称为collection initializer,如果您这样做,它将在您的初始示例中起作用:

List<int> test = new List<int> { 1, 2, 3 };

您确实需要更新一些东西才能使用这种语法,即集合初始化器只能出现在对象创建表达式的上下文中。在 C# 规范第 7.6.10.1 节中,这是对象创建表达式的语法:

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 节):

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

现在,您真正缺少的是某种列表文字,它非常方便。我为可枚举项 here 提出了一种这样的文字。

【讨论】:

  • 是的,完全正确。原始代码在语法上是正确的,但实际上没有 List 可以将这些项目放入其中。
  • 你的最后一个例子是众所周知的。但我不知道你可以在没有new MyCollection 的对象初始化器中使用集合初始化器。您链接的 MSDN 文章也没有提到这一点。
  • 另一个很好的答案。我猜他们必须这样做以允许添加项目而不覆盖可能已在初始对象的构造函数中添加的值。这有点模棱两可,因为您希望 = 操作的结果值与右侧赋值完全匹配,但在这种情况下它只是添加一组值。
  • @CodeInChaos:您必须查看 C# 语法才能看到。我已经适当地更新了。
【解决方案4】:
var test = (new [] { 1, 2, 3}).ToList();

【讨论】:

  • 他不是在问如何让它工作,而是在问为什么原始程序甚至可以编译。
  • 它编译是因为它是有效的 C#,它在运行时失败,因为他没有实例化列表对象
  • 接下来我们会问为什么除以零编译以及为什么 C# 编译器没有捕捉到,对吧
  • @K Ivanov:您可能想尝试在答案中而不是在 cmets 中回答问题。当然,其他人现在已经回答得更好了。仅供参考,c# 编译器确实捕获了基本除以零错误。例如int i = 1 / 0; 无法编译。
  • @Al Kepp:在我看来,答案的根源不是他没有实例化列表对象,而是编译器将初始化程序中的 List = Array 类型的东西变成了一系列单独的添加而不是仅仅将其视为单个作业。如果这对您来说很明显,那么很好,但从支持该问题的人数来看,这显然对所有人来说并不明显。
【解决方案5】:

原因是第二个示例是成员列表初始化程序 - System.Linq.Expressions 中的 MemberListBinding 表达式对此进行了深入了解 - 请参阅我对其他问题的回答以获取更多详细信息:What are some examples of MemberBinding LINQ expressions?

这种类型的初始化器要求列表已经初始化,以便可以将您提供的序列添加到其中。

因此——从语法上讲,代码绝对没有问题——NullReferenceException 是一个运行时错误,它是由未实际创建的 List 引起的。 news 列表的默认构造函数,或代码体中的内联 new,将解决运行时错误。

至于为什么这与第一行代码之间存在差异 - 在您的示例中这是不允许的,因为这种类型的表达式不能位于赋值的右侧,因为实际上并没有 创建任何东西,它只是Add 的简写。

【讨论】:

  • 我看了一下链接。获得更深入了解事情实际完成方式的好东西。谢谢!至于我的例子,我认为现在我得到的所有答案都很清楚:)
【解决方案6】:

将您的代码更改为:

class Test
{
   public List<int> Field = new List<int>();
}

原因是您必须先显式创建一个集合对象,然后才能将项目放入其中。

【讨论】:

  • 他不是在问如何让它工作,而是在问为什么原始程序甚至可以编译。
  • 它编译是因为它是正确的。你还想听什么?
  • @Anthony Pegram - 他是对的 - 使用的语法要求首先初始化 List,因为它是成员成员初始化器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-08
  • 2015-01-20
  • 1970-01-01
  • 2021-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多