在C#编译器中键入Duck
本文关键字:Duck 编译器 | 更新日期: 2023-09-27 17:59:12
注意这是一个关于如何在C#中实现或模拟鸭子类型的问题,而不是。。。
几年来,我一直认为某些C#语言特性依赖于语言本身定义的数据结构(对我来说,这总是一个奇怪的鸡和蛋的场景)。例如,我的印象是foreach
循环只能用于实现IEnumerable
的类型。
从那时起,我开始理解C#编译器使用鸭子类型来确定对象是否可以在foreach循环中使用,从而寻找GetEnumerator
方法而不是IEnumerable
方法。这是很有意义的,因为它去除了鸡肉和鸡肉;鸡蛋难题。
我有点困惑,为什么using
块和IDisposable
的情况似乎不是这样。编译器不能使用duck类型并查找Dispose
方法,有什么特别的原因吗?这种不一致的原因是什么?
也许IDisposable背后还有其他事情?
讨论为什么有一个对象的Dispose方法没有实现IDisposable超出了这个问题的范围:)
IDisposable
没有什么特别之处,但迭代器有一些特别之处。
在C#2之前,在foreach
上使用这种鸭子类型是实现强类型迭代器的唯一方法,也是在不装箱的情况下迭代值类型的唯一方法。我怀疑,如果C#和.NET一开始就有泛型,那么foreach
将需要IEnumerable<T>
,而不是鸭子类型。
现在编译器在我能想到的其他几个地方使用这种鸭子类型:
- 集合初始化器寻找合适的
Add
重载(以及必须实现IEnumerable
的类型,只是为了表明它确实是某种集合);这允许灵活地添加单个项、键/值对等 - LINQ(
Select
等)-这就是LINQ实现其灵活性的方式,允许对多种类型使用相同的查询表达式格式,而不必更改IEnumerable<T>
本身 - C#5等待表达式要求
GetAwaiter
返回一个具有IsCompleted
/OnCompleted
/GetResult
的awaiter类型
在这两种情况下,这使得将该功能添加到现有的类型和接口中变得更容易,而这些类型和接口在早期并不存在该概念
考虑到IDisposable
从第一个版本就已经在框架中了,我认为鸭子键入using
语句没有任何好处。我知道你明确地试图从讨论中忽略使用Dispose
而没有实现IDisposable
的原因,但我认为这是一个关键点。在语言中实现一个功能需要有充分的理由,我认为鸭子类型是一个超越支持已知接口的功能。如果这样做没有明显的好处,它就不会出现在语言中。
没有鸡和蛋:foreach
可以依赖IEnumerable
,因为IEnumerable
不依赖foreach
。允许在未实现IEnumerable
的集合上使用foreach的原因可能很大程度上是历史性的:
在C#中,它不是严格必要的用于要从中继承的集合类IEnumerable和IEnumerator按顺序排列与foreach兼容;只要因为该类具有所需的GetEnumerator、MoveNext、Reset和当前成员,它将与前臂。省略接口有允许您定义Current的返回类型比对象更具体,从而提供类型安全性。
此外,并不是所有的鸡和蛋问题实际上都是问题:例如,函数可以调用自己(递归!),或者引用类型可以包含自己(就像链表一样)。
那么,当using
出现时,当他们可以简单地说:实现IDisposable
时,为什么他们会使用像鸭子键入这样棘手的东西来指定呢?从根本上说,通过使用duck类型,您正在围绕类型系统进行最终运行,这只有在类型系统不足以(或不切实际)解决问题时才有用。
您所问的问题不是鸡和蛋的问题。它更像是语言编译器的实现方式。类似C#和VB.NET编译器的实现方式不同。如果你写了一个helloworld的简单代码,并用编译器和检查IL代码进行编译,它们会有所不同。回到您的问题,我想解释一下C#编译器为IEnumerable
生成了什么IL代码。
IEnumerator e = arr.GetEnumerator();
while(e.MoveNext())
{
e.Currrent;
}
因此,针对foreach
的情况对C#编译器进行了调整。