代码之家  ›  专栏  ›  技术社区  ›  Dan Tao

如果我只能定义一个getEnumerator,为什么要实现IEnumerable(T)?

  •  13
  • Dan Tao  · 技术社区  · 14 年前

    更新 :我感谢所有的评论,这些评论基本上都包含了一致的反对意见。尽管提出的每一个反对意见都是正确的,但我觉得棺材里的最后一颗钉子是 Ani's astute observation 最终,甚至 微小的 这个想法表面上提供的好处——消除样板代码——被这个想法本身需要的事实所否定。 拥有 样板代码。

    所以是的,我相信:这是个坏主意。

    只是为了某种程度上挽回我的尊严:我可能为了争论而把它夸大了,但我从来没有真正被出卖过这个想法——只是好奇听到别人怎么说。诚实。


    在你认为这个问题很荒谬之前,我请你考虑一下:

    1. IEnumerable<T> 继承自* IEnumerable ,这意味着实现 IEnumerable<t> 一般必须执行 二者都 IEnumerable<T>.GetEnumerator (明确) IEnumerable.GetEnumerator . 这基本上等于样板代码。
    2. 你可以 foreach 任何类型的 GetEnumerator 方法,只要该方法返回具有 MoveNext 方法和A Current 属性。所以如果你的类型定义了 带签名的方法 public IEnumerator<T> GetEnumerator() ,使用 前臂 .
    3. 显然,有很多代码需要 IEnumerable<t> 接口——例如,基本上所有的LINQ扩展方法。幸运的是,从一个你可以 前臂 关于 IEnumerable<t> 使用C通过 yield 关键字。

    所以,把这些放在一起,我有一个疯狂的想法:如果我只是定义自己的界面,看起来像这样:

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

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

    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 每种方法的实现。

    例如:

    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<T> 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(IEnumerable) 界面);但它突然出现在我的脑海中,我看不到这种方法会带来任何明显的问题。

    一般来说,如果我能写适量的代码 一旦 为了省去写少量代码的麻烦 很多次 对我来说,这是值得的。

    *这是正确的术语吗?我总是犹豫不决地说一个接口“继承”另一个接口,因为这似乎不能正确地捕获它们之间的关系。但也许它是对的。

    6 回复  |  直到 14 年前
        1
  •  5
  •   Ani    14 年前

    你不只是把样板搬到别的地方去了吗-从写 IEnumerable.GetEnumerator 方法来调用 AsEnumerable 每次延期 IEnumerable<T> 是预期的吗?通常,我希望一个可枚举类型用于查询的次数远多于它被写入的次数(正好一次)。这意味着这种模式将导致 更多 平均来说,是样板。

        2
  •  13
  •   Reed Copsey    14 年前

    你错过了一件大事-

    如果您实现自己的接口而不是 IEnumerable<T> ,您的类将无法使用预期的框架方法 IEnumerable<t> -主要地,您将完全无法使用Linq,或者使用类来构造 List<T> 或许多其他有用的抽象。

    正如您所提到的,您可以通过一个单独的扩展方法来实现这一点——但是,这是有代价的。通过使用扩展方法转换为 IEnumerable<t> ,您添加了使用类所需的另一个抽象级别(这比编写类要频繁得多),并降低了性能(实际上,您的扩展方法将在内部生成一个新的类实现,这实际上是不必要的)。最重要的是,类中的任何其他用户(或稍后的您)都必须学习一个没有任何效果的新API,因为它违反了用户的期望,所以不使用标准接口会使类更难使用。

        3
  •  9
  •   Jon Skeet    14 年前

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

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

    此外,尽管您的扩展方法允许您转换 IForEachable<T> 变成一个 IEnumerable<T> ,这意味着你的类型本身 不会 满足如下一般约束:

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

    或者类似的。基本上,通过执行转换,您将失去将单个对象视为 二者都 实现 IEnumerable<t> 真正的混凝土类型。

    另外,不执行 IEnumerable ,您正在从集合初始值设定项中计数自己。(我有时执行 IEnumerable(IEnumerable) 明确地 只是 若要选择进入集合初始化,请从 GetEnumerator() )

    哦,你还介绍了一个世界上其他人都不熟悉的额外的基础设施,与那些已经知道 IEnumerable<t> .

        4
  •  5
  •   Rex M    14 年前

    在我们的代码库中,可能有100多个这段代码片段的实例:

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

    我很同意。为与其他所有编写过的.NET代码完全兼容而付出的代价是微乎其微的;)

    您提出的解决方案基本上要求新接口的每个使用者/调用者也记住在调用前对其调用一个特殊的扩展方法。 有用的 .

        5
  •  1
  •   ChaosPandion    14 年前

    使用起来容易多了 IEnumerable 为了反思。试图通过反射调用通用接口是如此痛苦。拳击VIA的处罚 IEnumerable(IEnumerable) 由于反射本身的开销而丢失,那么为什么还要使用通用接口呢?例如,我们想到了序列化。

        6
  •  0
  •   Jimmy Hoffa    14 年前

    我认为每个人都已经提到了不这样做的技术原因,所以我将把它添加到组合中:要求您的用户对集合调用AsEnumerable()才能使用可枚举扩展将违反最不令人惊讶的原则。