更新: :我感谢所有的评论,这些评论本质上包括一致反对。虽然提出的每一个异议都是有效的,但我觉得棺材中的终极钉子是 ANI的精明观察 最终,甚至 微小 表面上提出的这个想法的好处 - 消除了样板代码 - 该想法本身需要它的事实否定了 自己的 样板代码。

是的,考虑到我说服了:这将是一个坏主意。

只是为了挽救我的尊严:我可能是为了争论而演奏的,但是我从来没有真正出售过这个想法 - 只是很好奇听到别人对此有何评论。诚实的。


在您将这个问题视为荒谬之前,我要求您考虑以下内容:

  1. IEnumerable<T> 从*继承 IEnumerable, ,这意味着任何实施的类型 IEnumerable<T> 通常必须实施 两个都 IEnumerable<T>.GetEnumerator (明确) IEnumerable.GetEnumerator. 。这基本上等于样板代码。
  2. 你可以 foreach 在任何具有 GetEnumerator 方法,只要该方法返回某种类型的对象, MoveNext 方法和 Current 财产。因此,如果您的类型定义 签名的方法 public IEnumerator<T> GetEnumerator(), ,使用 foreach.
  3. 显然,那里有很多代码需要 IEnumerable<T> 接口 - 例如,基本上所有的LINQ扩展方法。幸运的是,从您可以的类型中走出来 foreach 在一个 IEnumerable<T> 使用自动迭代器生成C#通过 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> 界面,我只能定义一个扩展方法以从任何 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.GetEnumerator每个班级的方法来调用您的 AsEnumerable 每次扩展 IEnumerable<T> 是期待?通常,我希望将一种枚举类型用于查询的次数远远超过写的次数(完全是一次)。这意味着这种模式将导致 更多的 样板平均。

其他提示

你错过了一件大事 -

如果您实现自己的界面而不是 IEnumerable<T>, ,您的班级将不适用于期望的框架方法 IEnumerable<T> - 主要是,您将完全无法使用LINQ或使用类构建 List<T>, ,或许多其他有用的抽象。

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

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

它还为迭代的每个步骤引入了额外的间接水平。 大概 这不是一个性能问题,但是当我认为您真的获得非常重要的事情时,仍然有些不必要。

另外,尽管您的扩展方法使您可以转换任何 IForEachable<T> 进入 IEnumerable<T>, ,这意味着您的类型本身 惯于 满足这样的通用约束:

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

之类的。基本上,通过必须执行转换,您将失去将单个对象视为的能力 两个都 实施 IEnumerable<T> 真正的混凝土类型。

另外,不实施 IEnumerable, ,您要算出收集初始化器。 (我有时会实施 IEnumerable 明确 只是 要选择收集初始化,请从 GetEnumerator().)

哦,与已经了解的C#开发人员相比 IEnumerable<T>.

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

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

我真的很好。与其他所有的.NET代码完全兼容付费是一个很小的价格;)

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

它更容易使用 IEnumerable 进行反思。试图通过反射调用通用界面是如此痛苦。拳击的罚款 IEnumerable 反射本身的开销丢失了,那么为什么要使用通用接口呢?至于一个例子,想到了序列化。

我认为每个人都已经提到了不执行此操作的技术原因,因此我将其添加到混音中:要求您的用户在收藏中调用Asenumerable()能够使用枚举的扩展名,这违反了最少惊喜的原则。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top