如果我可以定义一个GetEnumerator,为什么要实现IEnumerable(T)?

更新 :我感谢所有的评论,这些评论基本上都是一致的反对意见。 虽然提出的每一项反对意见都是有效的,但我觉得棺材中的最终指甲是Ani的敏锐观察 ,最终,即使是这个想法表面上提供的一个 微不足道的好处 – 消除样板代码 – 也被这个事实所否定。想法本身需要自己的样板代码。

所以,是的,请相信我说:这将是一个坏主意。

而且只是为了有点挽救我的尊严:我可能是为了争论而玩弄它,但我从来没有真正以这个想法开始出售 – 只是好奇地听到其他人不得不说的话。 诚实。


在你将这个问题视为荒谬之前,我请你考虑以下问题:

  1. IEnumerableinheritance自* IEnumerable ,这意味着任何实现IEnumerable通常必须实现IEnumerable.GetEnumerator (显式) IEnumerable.GetEnumerator 。 这基本上等于样板代码。
  2. 只要该方法使用MoveNext方法和Current属性返回某种类型的对象,您就可以foreach处理具有GetEnumerator方法的任何类型。 因此,如果您的类型使用签名public IEnumerator GetEnumerator()定义一个方法,则使用foreach枚举它是合法的。
  3. 显然,有很多代码需要IEnumerable接口 – 例如,基本上所有的LINQ扩展方法。 幸运的是,使用C#通过yield关键字提供的自动迭代器生成,从可以使用的类型转换为IEnumerable是微不足道的。

所以,把这一切放在一起,我有这个疯狂的想法:如果我只是定义我自己的界面,如下所示:

 public interface IForEachable { IEnumerator GetEnumerator(); } 

然后每当我定义一个我想要枚举的类型时,我实现这个接口而不是IEnumerable ,从而无需实现两个GetEnumerator方法(一个显式)。 例如:

 class NaturalNumbers : IForEachable { public IEnumerator GetEnumerator() { int i = 1; while (i < int.MaxValue) { yield return (i++); } } // Notice how I don't have to define a method like // IEnumerator IEnumerable.GetEnumerator(). } 

最后 ,为了使这种类型与期望IEnumerable接口的代码兼容,我可以定义一个扩展方法,从任何IForEachableIEnumerable如下所示:

 public static class ForEachableExtensions { public static IEnumerable AsEnumerable(this IForEachable source) { foreach (T item in source) { yield return item; } } } 

在我看来,这样做使我能够设计出各种方式可用的类型作为IEnumerable ,但每个方案中没有那个讨厌的显式IEnumerable.GetEnumerator实现。

例如:

 var numbers = new NaturalNumbers(); // I can foreach myself... foreach (int x in numbers) { if (x > 100) break; if (x % 2 != 0) continue; Console.WriteLine(x); } // Or I can treat this object as an IEnumerable implementation // if I want to... var evenNumbers = from x in numbers.AsEnumerable() where x % 2 == 0 select x; foreach (int x in evenNumbers.TakeWhile(i => i <= 100)) { Console.WriteLine(x); } 

你们怎么看待这个想法? 我错过了为什么这会是一个错误的原因吗?

我意识到这可能看起来像是一个过于复杂的解决方案,开始时没有那么大的交易(我怀疑任何人真的非常关心必须明确定义IEnumerable接口); 但它只是突然出现在我脑海中,我没有看到这种方法会带来任何明显的问题。

一般来说,如果我能编写一些适量的代码来节省自己多次编写少量代码的麻烦,对我来说,这是值得的。

*这是正确的术语吗? 我总是犹豫要说一个界面“inheritance”另一个界面,因为这似乎没有正确捕捉它们之间的关系。 但也许它是正确的。

您是不是只是将样板移动到其他位置 – 从每个类上的IEnumerable.GetEnumerator方法写入到每次预期IEnumerable时调用您的AsEnumerable扩展? 通常情况下,我希望使用可枚举类型查询的次数远远超过写入次数(这恰好是一次)。 这意味着这种模式平均会导致更多样板。

你错过了一件大事 –

如果你实现自己的接口而不是IEnumerable ,你的类将无法使用期望IEnumerable的框架方法 – 主要是,你将完全无法使用LINQ,或者使用你的类来构造List ,或许多其他有用的抽象。

正如您所提到的,您可以通过单独的扩展方法完成此任务 – 但是,这需要付出代价。 通过使用扩展方法转换为IEnumerable ,您需要添加另一个抽象级别才能使用您的类(您将比编写类更频繁地执行FAR),并且会降低性能(实际上,extension方法将在内部生成一个新的类实现,这实际上是不必要的)。 最重要的是,你class级的任何其他用户(或者你以后)都必须学习一种不会完成任何事情的新API – 你通过不使用标准接口使你的课程更难以使用,因为它违反了用户的期望。

你是对的:对于一个非常简单的问题来说, 似乎是一个过于复杂的解决方案。

它还为迭代的每个步骤引入了额外的间接级别。 可能不是性能问题,但当我认为你没有真正获得任何非常重要的东西时,仍然有点不必要。

此外,虽然您的扩展方法允许您将任何IForEachable转换为IEnumerable ,但这意味着您的类型本身不会满足这样的通用约束:

 public void Foo(T collection) where T : IEnumerable 

等等。 基本上,通过必须执行转换,您将失去将单个对象视为IEnumerable 真实具体类型的能力。

此外,通过不实现IEnumerable ,您将自己计算在集合初始化器之外。 (我有时会明确地实现IEnumerable以便选择进入集合初始化,从GetEnumerator()抛出exception。)

哦,你已经介绍了一些额外的基础设施,这是世界上其他人都不熟悉的,与已经了解IEnumerable的大量C#开发人员相比。

在我们的代码库中,可能有超过100个这个完整代码段的实例:

 IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } 

我真的很好。 为与所有其他.NET代码完全兼容而付出的代价是微不足道的;)

您提出的解决方案基本上要求您的新接口的每个消费者/调用者都记得在它有用之前调用特殊的扩展方法。

使用IEnumerable进行reflection要容易得多。 试图通过reflection调用通用接口是一件痛苦的事。 通过IEnumerable对拳击的惩罚因reflection本身的开销而丢失,那么为什么还要使用通用接口呢? 举个例子,我想到了序列化。

我想每个人都已经提到了不这样做的技术原因,所以我将把它添加到组合中:要求你的用户在你的集合上调用AsEnumerable()以便能够使用可枚举的扩展会违反最少惊喜的原则。