为什么C#集合初始化器以这种方式工作?
我正在查看C#集合初始化器,发现实现非常实用,但也非常不同于C#
我能够创build这样的代码:
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) { } }
因为我已经满足了编译器的最低要求(实现IEnumerable
和一个public void Add
),但是工作起来却显然没有任何价值。
我想知道是什么阻止了C#团队创build更严格的要求? 换句话说,为了编译这个语法,编译器不要求该types实现ICollection
吗? 这似乎更多的是其他C#function的精神。
你的观察是真实的 – 事实上,它反映了微软C#语言PM的Mads Torgersen所做的一个观察。
在2006年10月,Mads就这个题目是什么是一个集合? 他在其中写道:
我们承认,我们在System.Collections.ICollection的框架的第一个版本中吹嘘它,这是无用的。 但是当我们在.NET框架2.0中使用generics时,我们可以很好地解决它:System.Collections.Generic.ICollection <T>允许您添加和移除元素,枚举它们,计算它们并检查成员身份。
很显然,从那时起,每一次收集都会实行ICollection <T>,对不对? 不是这样。 下面是我们如何使用LINQ来了解集合究竟是什么,以及如何让我们改变我们在C#3.0中的语言devise。
事实certificate,在框架中只有14个ICollection<T>
的实现,但有189个实现IEnumerable
类有一个公共的Add()
方法。
这种方法有一个隐藏的好处 – 如果他们已经将其基于ICollection<T>
接口,就会有一个支持的Add()
方法。
相反,他们采取的方法意味着集合的初始化器只是为Add()
方法形成一组参数。
为了说明,我们稍微扩展你的代码:
class Test : IEnumerable { public IEnumerator GetEnumerator() { throw new NotImplementedException(); } public void Add(int i) { } public void Add(int i, string s) { } }
你现在可以写这个:
class Program { static void Main() { Test test = new Test { 1, { 2, "two" }, 3 }; } }
我也考虑过这个问题,最让我满意的答案是ICollection除了添加外还有许多方法,比如:Clear,Contains,CopyTo和Remove。 删除元素或清除与能够支持对象初始值设定语法无关,您只需要一个Add()。
如果框架的devise足够精细,并且有一个ICollectionAdd接口,那么它会有一个“完美”的devise。 但我真的不认为这会增加很多价值,每个接口有一个方法。 IEnumerable + Add似乎是一种骇人听闻的方式,但是当你考虑一下时,这是一个更好的select。
编辑:这不是唯一的时间C#已经接近这种types的解决scheme的问题。 从.NET 1.1开始,foreach使用duck typing来枚举一个集合,所有你需要实现的类是GetEnumerator,MoveNext和Current。 基里尔·奥森科夫也有一个post提出你的问题。
(我知道我迟到了3年,但是我对现有的答案并不满意。)
为什么为了编译这个语法,编译器不要求types实现ICollection?
我会扭转你的问题:如果编译器需要的东西不是真的需要的话,会有什么用?
非ICollection
类也可以从集合初始值设定语法中受益。 考虑允许向其中添加数据的类,而不允许访问之前添加的数据。
就个人而言,我喜欢使用new Data { { ..., ... }, ... }
语法为我的unit testing的代码添加一个类似DSL的外观。
实际上,我宁愿削弱这个需求,这样我就可以使用漂亮的语法,甚至不必执行IEnumerable
。 集合初始化器是纯粹的语法糖Add(),他们不应该需要任何东西。