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

时间:2010-09-17 18:35:19

标签: c# .net foreach ienumerable ienumerator

更新:我感谢所有评论,这些评论基本上都是一致的反对意见。虽然提出的每一项反对意见都是有效的,但我觉得棺材中的最终指甲是Ani's astute observation,最终,即使是一个 微小这个想法表面上提供的好处 - 消除样板代码 - 由于该想法本身需要其自己的样板代码而被否定。

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

只是为了有点挽救我的尊严:我可能是出于争吵的目的而玩弄它,但我从来没有真正把这个想法卖掉 - 只是好奇地听到其他人不得不说的话。诚实。


在你认为这个问题荒谬之前,我请你考虑以下几点:

  1. IEnumerable<T>继承自* IEnumerable,这意味着任何实现IEnumerable<T>的类型通常都必须实现 IEnumerable<T>.GetEnumerator 和< / em>(明确地)IEnumerable.GetEnumerator。这基本上等于样板代码。
  2. 您可以foreach覆盖任何具有GetEnumerator方法的类型,只要该方法使用MoveNext方法和Current属性返回某种类型的对象。因此,如果您的类型使用签名public IEnumerator<T> GetEnumerator()定义一个方法,则使用foreach对其进行枚举是合法的。
  3. 显然,有很多代码需要IEnumerable<T>接口 - 例如,基本上所有的LINQ扩展方法。幸运的是,使用C#通过foreach关键字提供的自动迭代器生成,从IEnumerable<T>yield的类型变得微不足道。
  4. 所以,把这一切放在一起,我有一个疯狂的想法:如果我只是定义我自己的界面,如果这样:

    public interface IForEachable<T>
    {
        IEnumerator<T> GetEnumerator();
    }
    

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

    GetEnumerator

    最后,为了使此类型与 期望class NaturalNumbers : IForEachable<int> { public IEnumerator<int> 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<T> IForEachable<T>就像这样:

    IEnumerable<T>

    在我看来,这样做可以让我设计出各种方式可用的类型作为public static class ForEachableExtensions { public static IEnumerable<T> AsEnumerable<T>(this IForEachable<T> source) { foreach (T item in source) { yield return item; } } } 的实现,但每个方案中没有那个讨厌的显式IEnumerable<T>实现。

    例如:

    IEnumerable.GetEnumerator

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

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

    一般情况下,如果我可以编写适量的代码一次,以免给自己省去多次编写少量代码的麻烦 ,这是值得的。

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

6 个答案:

答案 0 :(得分:13)

你错过了一件大事 -

如果您实现自己的界面而不是IEnumerable<T>,那么您的类将无法使用期望IEnumerable<T>的框架方法 - 主要是,您将完全无法使用LINQ,或者使用您的类来构造一个List<T>,或许多其他有用的抽象。

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

答案 1 :(得分:9)

你是对的: 似乎是一个过于复杂的解决方案,非常容易解决问题。

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

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

public void Foo<T>(T collection) where T : IEnumerable

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

此外,通过不实现IEnumerable,您将自己计算在集合初始值设定项之外。 (我有时会实现IEnumerable显式只是来选择进行集合初始化,从GetEnumerator()引发异常。)

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

答案 2 :(得分:5)

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

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

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

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

答案 3 :(得分:5)

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

答案 4 :(得分:1)

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

答案 5 :(得分:0)

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