关于c#:List< int>

List<int> test = {1, 2, 3} - is it a feature or a bug?

如您所知,不允许对列表使用数组初始化语法。它将给出编译时错误。例子:

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 = { 1, 2, 3 }被编译为调用存储在Test字段中的列表的Add方法的代码。

你得到NullReferenceException的原因是Testnull。如果将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是临时变量,在其他情况下是不可见和不可访问的。


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查询结果创建的对象的成员。


此代码:

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);

由于Fieldnull,所以得到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

现在,您真正缺少的是某种列表文字,这将非常方便。我在这里提出了一个这样的字面上的易读词。


1
var test = (new [] { 1, 2, 3}).ToList();


原因是第二个示例是成员列表初始化器,System.Linq.Expressions中的MemberListBinding表达式对此提供了深入的了解-有关详细信息,请参阅我对另一个问题的回答:MemberBinding Linq表达式的一些示例是什么?

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

因此,从语法上讲,代码完全没有问题,NullReferenceException是一个运行时错误,它是由于列表没有实际创建而导致的。由new作为列表的默认构造函数,或代码体中的内联new将解决运行时错误。

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


将代码更改为:

1
2
3
4
class Test
{
   public List<int> Field = new List<int>();
}

原因是,必须先显式创建集合对象,然后才能向其放置项。