C#编译器中的Duck类型

C#编译器中的Duck类型,c#,compiler-construction,idisposable,duck-typing,C#,Compiler Construction,Idisposable,Duck Typing,注意这不是关于如何在C#中实现或模拟duck类型的问题 几年来,我一直认为某些C#语言特性对该语言本身定义的数据结构(对我来说,这似乎总是一个奇怪的鸡和蛋的场景)是有害的。例如,我的印象是foreach循环只能用于实现IEnumerable的类型 IEnumerator e = arr.GetEnumerator(); while(e.MoveNext()) { e.Currrent; } 从那时起,我开始理解C编译器使用duck类型来确定对象是否可以在foreach循环中使用,寻找G

注意这不是关于如何在C#中实现或模拟duck类型的问题

几年来,我一直认为某些C#语言特性对该语言本身定义的数据结构(对我来说,这似乎总是一个奇怪的鸡和蛋的场景)是有害的。例如,我的印象是
foreach
循环只能用于实现
IEnumerable
的类型

IEnumerator e = arr.GetEnumerator();

while(e.MoveNext())
{
   e.Currrent;
}
从那时起,我开始理解C编译器使用duck类型来确定对象是否可以在foreach循环中使用,寻找
GetEnumerator
方法,而不是
IEnumerable
。这很有意义,因为它消除了鸡和蛋的难题

我有点困惑,为什么这不是
使用
块和
IDisposable
的情况。编译器不能使用duck类型和查找
Dispose
方法有什么特殊原因吗?这种不一致的原因是什么

也许在引擎盖下还有其他事情发生在IDisposable身上


讨论为什么你会有一个没有实现IDisposable的Dispose方法的对象不在这个问题的范围之内:)

没有鸡和蛋:
foreach
可以依赖于
IEnumerable
,因为
IEnumerable
不依赖于
foreach
。在未实现
IEnumerable
的集合上允许使用foreach的原因可能是:

在C#中,这并不是严格必要的 对于要从中继承的集合类 按顺序排列的IEnumerable和IEnumerator 与foreach兼容;只要 由于该类具有所需的 GetEnumerator、MoveNext、Reset和 目前的成员,它将与 弗雷奇。省略接口会导致错误 允许您 定义当前目标的返回类型 因此,要比对象更具体 提供类型安全

此外,并非所有的鸡和蛋问题实际上都是问题:例如,函数可以调用自身(递归!),或者引用类型可以包含自身(如链表)


所以当
使用
出现时,为什么他们会使用像duck键入这样棘手的东西,而他们可以简单地说:implement
IDisposable
?从根本上说,通过使用duck类型,您正在围绕类型系统进行结束运行,这只有在类型系统不足以(或不实用)解决问题时才有用。

这里的IDisposable没有什么特别之处,但迭代器有一些特别之处

在C#2之前,在
foreach
上使用此duck类型是唯一可以实现强类型迭代器的方法,也是在没有装箱的情况下迭代值类型的唯一方法。我怀疑如果C#和.NET一开始就有泛型,
foreach
将需要
IEnumerable
,而不是duck类型

现在,编译器在我能想到的几个其他地方使用这种duck类型:

  • 集合初始值设定项寻找合适的
    Add
    重载(以及必须实现
    IEnumerable
    的类型,只是为了表明它确实是某种集合);这允许灵活添加单个项目、键/值对等
  • LINQ(
    Select
    etc)-这就是LINQ实现其灵活性的方式,允许针对多种类型使用相同的查询表达式格式,而无需更改
    IEnumerable
    本身
  • C#5 wait表达式要求
    GetAwaiter
    返回一个已完成
    IsCompleted
    /
    OnCompleted
    /
    GetResult
在这两种情况下,这使得将特性添加到现有类型和接口中变得更容易,而在这些类型和接口中,概念在早期并不存在


鉴于自第一个版本起,
IDisposable
就一直在框架中,我认为使用
语句键入
不会有任何好处。我知道您在讨论中明确地试图忽略使用
Dispose
而不实现
IDisposable
的原因,但我认为这是一个关键点。在该语言中实现一个特性需要有充分的理由,我认为duck类型是一个超越支持已知接口的特性。如果这样做没有明显的好处,它就不会出现在语言中。

你问的问题不是鸡和蛋的情况。它更像是语言编译器是如何实现的。像C#和VB.NET一样,编译器的实现是不同的。如果您编写一个简单的hello world代码,并使用编译器和检查IL代码对其进行编译,它们将是不同的。回到你的问题,我想解释一下C编译器为
IEnumerable
生成的IL代码是什么

IEnumerator e = arr.GetEnumerator();

while(e.MoveNext())
{
   e.Currrent;
}

因此,针对
foreach
的情况对C#编译器进行了调整

+1有趣。你能提供一个关于
foreach
的信息来源吗?@harpo我刚刚“发现”了MattDavey所做的事情。我在下面发布了一些代码作为答案,虽然从技术上讲这本身并不是一个答案。Eric Lippert在被问到这个问题后不久就写了一篇关于这个问题的博客文章:下面是关于foreach的一点很好的解释。简而言之,对象不需要实现
IEnumerable
接口,它们只需要一个
GetEnumerator()
方法,该方法返回一个具有
MoveNext()的对象
Current
@MattDavey该链接不起作用。我所指的鸡和蛋场景是,语言功能可能取决于语言本身定义的类型。IEnumerable依赖于C#,而C#(foreach)依赖于IEnumerable……从这个意义上讲,很多东西都是鸡肉+鸡蛋: