13

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

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

只是为了在某种程度上挽救我的尊严:我可能为了争论而夸大了它,但我从来没有真正相信过这个想法——只是好奇地想听听其他人对此的看法。诚实。


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

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

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

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

然后,每当我定义一个我想要可枚举的类型时,我都会实现这个接口而不是IEnumerable<T>,从而无需实现两个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>可以定义一个扩展方法来从 anyIForEachable<T>变为IEnumerable<T>like:

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接口);但它只是突然出现在我的脑海中,我没有看到这种方法会带来任何明显的问题。

一般来说,如果我可以一次编写适量的代码,省去多次编写少量代码的麻烦,对我来说,这是值得的。

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

4

6 回答 6

13

你错过了一件大事 -

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

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

于 2010-09-17T18:39:54.803 回答
9

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

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

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

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

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

此外,如果不实施IEnumerable,您会将自己排除在集合初始化程序之外。(我有时IEnumerable明确地实现只是为了选择集合初始化,从.中抛出异常GetEnumerator()。)

哦,与已经了解IEnumerable<T>.

于 2010-09-17T18:40:24.140 回答
5

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

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

我对此非常满意。与曾经编写的所有其他 .NET 代码完全兼容,这是一个很小的代价;)

您提出的解决方案本质上要求新接口的每个使用者/调用者还记得在它有用之前对其调用特殊的扩展方法。

于 2010-09-17T18:39:55.673 回答
5

您不只是将样板移动到其他地方吗 - 从IEnumerable.GetEnumerator在每个类上编写方法到每次需要调用您的AsEnumerable扩展IEnumerable<T>?通常,我希望可枚举类型用于查询的次数远远超过它的写入次数(恰好是一次)。这意味着这种模式平均会导致更多样板。

于 2010-09-17T18:44:31.330 回答
1

它更容易IEnumerable用于反射。试图通过反射调用泛型接口是一件很痛苦的事情。由于反射本身的开销而丢失了装箱IEnumerable的代价,那么为什么还要使用通用接口呢?举个例子,我想到了序列化。

于 2010-09-17T18:38:02.407 回答
0

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

于 2010-09-17T18:55:33.533 回答