集合初始化器和TypeInitializationException与IEnumerable A collection initializer is a syntax in C# that allows you to initialize a collection object with a set of values in a concise and readable way. It is denoted by curly braces {} and can be used with various
集合初始化器和TypeInitializationException与IEnumerable A collection initializer is a syntax in C# that allows you to initialize a collection object with a set of values in a concise and readable way. It is denoted by curly braces {} and can be used with various
我正在研究C#的集合初始化器,并发现其实现非常务实,但与C#中的其他内容非常不同。\n我可以创建如下代码:\n
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) { } }
\n由于我满足了编译器的最低要求(实现了IEnumerable
和public void Add
),这段代码可以工作,但显然没有价值。\n我想知道是什么阻止了C#团队创建一个更严格的要求?换句话说,为什么为了编译这个语法,编译器不要求类型实现ICollection
?这似乎更符合其他C#特性的精神。
问题的原因是:为了使集合初始化器语法编译通过,编译器不需要类型实现ICollection接口。
解决方法是:可以考虑使用非ICollection类来使用集合初始化器语法。非ICollection类也可以从集合初始化器语法中受益。可以考虑实现ICollectionInitializerSyntax
文章整理如下:
在集合初始化器语法中,为什么编译器不要求类型实现ICollection接口?其实,如果编译器有一些不真正需要的要求,那有什么意义呢?
非ICollection类也可以从集合初始化器语法中受益。考虑那些允许向其中添加数据但不允许访问先前添加的数据的类。个人而言,我喜欢在我的单元测试代码中使用new Data { { ..., ... }, ... }
这样的语法,使代码看起来更加简洁、类似于DSL。
实际上,我更希望减弱这个要求,这样我就可以使用这种漂亮的语法而不必实现IEnumerable接口。集合初始化器只是对Add()方法的纯语法糖,不应该要求其他任何东西。
我理解你的观点,但是如果语法糖需要一个方法,我更愿意通过一个接口来了解这一点。
像ICollectionInitializerSyntax
ICollection接口除了Add方法之外,还有很多其他的方法,如Clear、Contains、CopyTo和Remove。删除元素或清除元素与是否能够支持对象初始化语法无关,你只需要一个Add()方法。如果框架设计足够细致,并且有一个ICollectionAdd接口,那么它就会有一个“完美”的设计。但是老实说,我认为这并没有增加太多的价值,每个接口只有一个方法。IEnumerable + Add看起来像是一个hackish的方法,但是当你思考一下,它是一个更好的选择。
这并不是C#第一次用这种类型的解决方案来处理问题。自从.NET 1.1以来,foreach使用鸭子类型来枚举一个集合,你的类只需要实现GetEnumerator、MoveNext和Current方法。Kirill Osenkov在他的博客中也提出了这个问题。
这就是“接口隔离原则”。
文章标题:集合初始化器和TypeInitializationException vs IEnumerable
根据Mads Torgersen(微软C#语言PM)在2006年10月发表的一篇博文“什么是集合?”可以看出你的观察是正确的。在这篇博文中,他写道:“我们承认,在框架的第一个版本中,System.Collections.ICollection几乎没有用处。但是当泛型出现在.NET Framework 2.0中时,我们对它进行了很好的修复:System.Collections.Generic.ICollection
实际上,.NET Framework中只有14个ICollection
采用这种方法有一个隐藏的好处——如果基于ICollection
为了说明问题,让我们稍微修改一下你的代码:
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 }; } }
你的回答展示了这种方法提供的好处和灵活性,但我不禁想知道在类似情况下是否有一种有效的方法提供这种灵活性。他们实际上在规范中写入了“如果在实现IEnumerable的层次结构中偶然有一个名为Add的方法,那么我们将在编译器中进行某种诡计,使初始化器调用它,即使该方法不是重载、接口成员或通过语法、属性或其他方式指定为此目的的方法。”
换句话说,我认为提问者的问题“C#团队为什么没有创建一个更严格的要求?”是有一定开放性的。你提出了一个很好的理由,说明为什么现在的方法提供了很多灵活性。回答这个问题基本上需要 1) 一份详尽的可能替代方案列表以及为什么它们不太适用,你有所涉及,或者 2) 一个在没有编译器诡计的情况下实现相同好处的示例。从API/插件设计的角度来看,我很好奇是否存在第2种方法。也就是说,你实现了几个参数的委托,我的框架将选择最佳的。
并不是说我希望回答实际上能实现1或2,因为你永远不知道你是否有完整的列表来回答1,或者是否存在第2种方法。这并不是对你很好的回答的批评。只是C#以这种方式实现似乎令人惊讶。我今天才刚刚阅读了这个功能的规范,它似乎与C#的特性不太相符。
实际上,事实并不像你想的那么离谱——例如,追溯到最初的C#实现,foreach关键字不需要IEnumerable的实现,GetEnumerator()的实现就足够了。他们在几个其他领域也保持了这种灵活性,包括在C# 5.0中实现的async和await。
我认为对于提问者的问题“C#团队为什么没有创建一个更严格的要求?”的答案可以从Mads的帖子中推测出来——他们希望创建一个真正有用的功能,而不仅仅是大多数人认为毫无价值的语法好奇。
原文来源:https://stackoverflow.com/questions/2262997