66

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

几年来,我的印象是某些 C# 语言特性依赖于语言本身定义的数据结构(在我看来,这总是一个奇怪的鸡和蛋的场景)。例如,我的印象是foreach循环仅可用于已实现的类型IEnumerable

从那时起,我开始明白 C# 编译器使用鸭子类型来确定对象是否可以在 foreach 循环中使用,寻找GetEnumerator方法而不是IEnumerable. 这很有意义,因为它消除了鸡和蛋的难题。

我有点困惑,为什么using块和IDisposable. 编译器是否有任何特殊原因不能使用鸭子类型并寻找Dispose方法?这种不一致的原因是什么?

也许 IDisposable 的幕后还有其他事情发生?

讨论为什么你会有一个没有实现 IDisposable 的 Dispose 方法的对象超出了这个问题的范围:)

4

3 回答 3

44

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

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

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

  • 集合初始化器寻找合适的Add重载(以及必须实现的类型IEnumerable,只是为了表明它确实是某种集合);这允许灵活添加单个项目、键/值对等
  • LINQ(Select等)——这就是 LINQ 实现其灵活性的方式,允许针对多种类型使用相同的查询表达式格式,而无需更改IEnumerable<T>自身
  • C# 5 的 await 表达式需要GetAwaiter返回一个具有IsCompleted/ OnCompleted/GetResult

在这两种情况下,这都使得将特性添加到现有类型和接口中变得更加容易,在这些类型和接口中,早期并不存在这个概念。

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

于 2011-06-16T08:24:57.490 回答
15

没有鸡和蛋: foreach可以依赖,IEnumerable因为IEnumerable不依赖foreach。在未实现的集合上允许 foreach 的原因IEnumerable可能在很大程度上是历史性的:

在 C# 中,为了与 foreach 兼容,集合类不是必须从 IEnumerable 和 IEnumerator 继承;只要该类具有所需的 GetEnumerator、MoveNext、Reset 和 Current 成员,它就可以与 foreach 一起使用。省略接口的优点是允许您将 Current 的返回类型定义为比对象更具体,从而提供类型安全。

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

因此,using当他们可以简单地说:实现时,为什么他们会使用像鸭子打字这样棘手的东西来指定IDisposable?从根本上说,通过使用鸭子类型,您正在围绕类型系统进行最终运行,这仅在类型系统不足以(或不切实际)解决问题时才有用。

于 2011-06-16T08:24:56.643 回答
0

您要问的问题不是先有鸡还是先有蛋的情况。它更像是语言编译器的实现方式。就像 C# 和 VB.NET 编译器的实现方式不同。如果您编写一个简单的 hello world 代码并使用编译器编译它并检查 IL 代码,它们会有所不同。回到你的问题,我想解释一下 C# 编译器为IEnumerable.

IEnumerator e = arr.GetEnumerator();

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

因此 C# 编译器针对foreach.

于 2011-06-16T08:44:17.427 回答