为什么C#集合collection 初始值设定项是这样工作的?

Why do C# collection initializers work this way?

我研究了C集合初始值设定项,发现实现非常实用,但也与C中的其他任何东西都非常不同。#

我可以创建这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.Collections;

class Program
{
    static void Main()
    {
        Test test = new Test { 1, 2, 3 };
    }
}

class Test : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public void Add(int i) { }
}

因为我已经满足了编译器(实现了IEnumerablepublic void Add的最低要求),所以这是可行的,但显然没有价值。

我想知道是什么阻止了C团队创建更严格的需求集?换句话说,为什么为了编译这种语法,编译器不要求类型实现ICollection?这似乎更符合其他C特性的精神。


你的观察是现场观察——事实上,它反映了微软C语言PM MadsTorgersen所做的一个观察。

Mads在2006年10月发表了一篇题为"什么是收藏"的文章。其中他写道:

Admitted, we blew it in the first
version of the framework with
System.Collections.ICollection, which
is next to useless. But we fixed it up
pretty well when generics came along
in .NET framework 2.0:
System.Collections.Generic.ICollection
lets you Add and Remove elements,
enumerate them, Count them and check
for membership.

Obviously from then on, everyone would
implement ICollection every time
they make a collection, right? Not so.
Here is how we used LINQ to learn
about what collections really are, and
how that made us change our language
design in C# 3.0.

事实证明,框架中只有14个ICollection的实现,但是189个类实现了IEnumerable并有一个公共的Add()方法。

这种方法有一个隐藏的好处——如果它们基于ICollection接口,那么就只有一个支持的Add()方法。

相反,他们采用的方法意味着集合的初始值设定项只是Add()方法的参数集。

为了说明这一点,我们稍微扩展一下代码:

1
2
3
4
5
6
7
8
9
10
11
class Test : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public void Add(int i) { }

    public void Add(int i, string s) { }
}

现在您可以编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Program
{
    static void Main()
    {
        Test test
            = new Test
            {
                1,
                { 2,"two" },
                3
            };
    }
}


我也考虑过这个问题,最令我满意的答案是ICollection有许多方法,除了add,例如:clear、contains、copyto和remove。删除元素或清除与支持对象初始值设定项语法无关,您只需要一个add()。

如果框架设计得足够精细,并且有一个ICollectionadd接口,那么它将有一个"完美"的设计。但老实说,我不认为这会增加多少价值,每个接口都有一个方法。IEnumerable+Add看起来像是一种黑客方法,但是当你考虑它时,它是一个更好的选择。

编辑:这不是唯一一次C接触到这种解决方案的问题。由于.NET 1.1,foreach使用duck类型来枚举集合,因此您需要实现的所有类都是getEnumerator、moveNext和current。基里尔·奥森科夫也有一个帖子问你问题。


(我知道我迟了3年,但我对现有的答案不满意。)

why, in order for this syntax to compile, does the compiler not
require that the type implement ICollection?

我将回答您的问题:如果编译器有一些实际上不需要的需求,它会有什么用途?

ICollection类也可以从集合初始值设定项语法中受益。考虑允许向其中添加数据的类,而不允许访问以前添加的数据。

就我个人而言,我喜欢使用new Data { { ..., ... }, ... }语法为我的单元测试代码添加一个轻量级的、类似DSL的外观。

实际上,我宁愿弱化需求,这样我就可以使用美观的语法,而不必费心实现IEnumerable。集合初始值设定项是纯语法上的sugar to add(),它们不需要任何其他内容。