鸭编入C#编译器
注意:这不是一个关于如何在C#中实现或模拟鸭子打字的问题。
几年来,我的印象是某些C#语言特性在语言本身定义的数据结构上是压缩的(对我来说,这总是看起来像一个奇怪的鸡鸡蛋)。 例如,我的印象是, foreach
循环只能用于实现IEnumerable
types。
从那时起我就开始理解C#编译器使用鸭子打字来确定一个对象是否可以在foreach循环中使用,寻找一个GetEnumerator
方法而不是IEnumerable
。 这消除了鸡蛋和鸡蛋的难题,这很有意义。
我有点困惑,为什么这不是似乎与using
块和IDisposable
的情况下。 编译器是否有任何特殊原因不能使用鸭子打字和查找Dispose
方法? 这种不一致的原因是什么?
也许在IDisposable的背后还有其他的东西呢?
讨论为什么你有一个Dispose方法没有实现IDisposable的对象超出了这个问题的范围:)
这里没有什么特别的关于IDisposable
– 但是迭代器有一些特别的地方。
在C#2之前,在foreach
上使用这个鸭子types是唯一的你可以实现一个强types的迭代器,也是没有装箱迭代值types的唯一方法。 我怀疑 ,如果C#和.NET有generics开始, foreach
将需要 IEnumerable<T>
,而不是鸭子打字。
现在编译器在我能想到的其他几个地方使用这种鸭子打字:
- 集合初始化器寻找一个合适的
Add
重载(以及types必须实现IEnumerable
,只是为了表明它确实是某种集合); 这允许灵活地添加单个项目,键/值对等 - LINQ(
Select
等) – 这是LINQ如何实现其灵活性,允许针对多种types的相同的查询expression式格式,而不必改变IEnumerable<T>
本身 - C#5的awaitexpression式要求
GetAwaiter
返回一个具有IsCompleted
/OnCompleted
/GetResult
的awaitertypes
在这两种情况下,都可以更轻松地将该function添加到现有的types和界面,而这些概念在之前不存在。
鉴于IDisposable
从第一个版本开始就已经在框架中,我不认为用鸭子inputusing
语句会有什么好处。 我知道你明确地试图把讨论中没有实现IDisposable
Dispose
的原因打折扣,但我认为这是一个关键点。 需要有足够的理由在语言中实现一个function,我认为鸭子打字是支持一个已知接口的一个function。 如果这样做没有明确的好处,那么最终不会以语言结束。
没有鸡和蛋: foreach
可以依靠IEnumerable
因为IEnumerable
不依赖于foreach
。 对于不执行IEnumerable
集合,允许使用foreach的原因大概是历史性的 :
在C#中,为了与foreach兼容,集合类从IEnumerable和IEnumeratorinheritance并不是必须的。 只要该类具有所需的GetEnumerator,MoveNext,Reset和Current成员,它将使用foreach。 省略接口的优点是允许您定义Current的返回types比对象更具体,从而提供types安全性。
此外,并非所有的鸡和鸡蛋问题实际上都是问题:例如一个函数可以自己调用(recursion!),或者一个引用types可以包含自身(如链表)。
所以在using
时,他们为什么会用棘手的方式来指定鸭子打字时,他们可以简单地说:执行IDisposable
? 从根本上说,通过使用鸭式input,你可以在types系统的末尾运行,这只有在types系统不足以解决问题时才有用。
你所问的问题不是鸡和鸡蛋的情况。 它更像是如何实现语言编译器。 就像C#和VB.NET编译器的实现是不同的。如果你编写一个hello world的简单代码,并用编译器编译它并检查IL代码,它们将会不同。 回到你的问题,我想解释一下IE代码是由C#编译器为IEnumerable
生成的。
IEnumerator e = arr.GetEnumerator(); while(e.MoveNext()) { e.Currrent; }
所以C#编译器针对foreach
的情况进行了调整。