3

IEnumerable<T>我有一个 C# 类,它需要在一堆方法中处理一系列项目( ),所以我不能简单地foreach在一个方法中。我调用.GetEnumerator()并传递它IEnumerator<T>,它在循环单个序列时为我提供了所需的灵活性。

现在我想允许其他人在这个过程中添加逻辑。最自然的方法是给他们一个接口,该接口带有一个接受IEnumerator<T>. 简单,完成,并且有效。

但我担心这是一种反模式。他们必须知道IEnumerator<T>已经.MoveNext()调用过,所以他们可以简单地访问.Current。另外,我没有看到任何IEnumerator<T>在要实现的接口中使用的先例。

  1. 我没有考虑哪些陷阱?
  2. 是否有另一种模式可以让我使用同样有效的机制(即我不希望创建/销毁多个副本)而不暴露IEnumerator<T>自身?

更新:正如我在下面的评论中提到的:我想要的是某种通用的Stream<T>. 我需要能够有效地查看下一项(IEnumerator.Current-> .Peek())并使用它(IEnumerator<T>.MoveNext()-> .Pop())。

我使用IEnumerator<T>它是因为它适合账单界面。我更喜欢在合适的情况下使用常见的 BCL 类型,但似乎我在滥用这种类型。

所以问题3)是否有适合这种需求的课程?还是我应该创建自己的 Stream 懒惰地在IEnumerator<T>内部执行?然后它将被完全封装。我不想使用许多现有的集合,因为它们有内部存储,而我希望存储成为IEnumerable<T>iteslf。


好吧,这听起来像共识是,做IEnumerator<T>往往是一个ValueType以及不知道先验状态的IEnumerator<T>,传递它通常是一个坏主意。

我听到的最好的建议是创建我自己的类,然后传递。还有其他建议吗?

4

4 回答 4

6

绝对不应该IEnumerator<T>四处走动。除此之外,在某些情况下,它可能会产生一些非常奇怪的影响。例如,您希望在这里发生什么?

using System;
using System.Collections.Generic;

class Test
{
    static void ShowCurrentAndNext(IEnumerator<int> iterator)        
    {
        Console.WriteLine("ShowCurrentAndNext");
        Console.WriteLine(iterator.Current);
        iterator.MoveNext(); // Let's assume it returns true
        Console.WriteLine(iterator.Current);
    }

    static void Main()
    {
        List<int> list = new List<int> { 1, 2, 3, 4, 5 };
        using (var iterator = list.GetEnumerator())
        {
            iterator.MoveNext(); // Get things going
            ShowCurrentAndNext(iterator);
            ShowCurrentAndNext(iterator);
            ShowCurrentAndNext(iterator);
        }
    }
}

尝试进行一些更改:

using (List<int>.Enumerator iterator = list.GetEnumerator())

using (IEnumerator<int> iterator = list.GetEnumerator())

尝试预测每种情况下的结果:)

诚然,这是一个特别邪恶的例子,但它确实展示了一些与传递可变状态相关的极端情况。我强烈建议您在“中央”方法中执行所有迭代,该方法仅使用当前值调用适当的其他方法。

于 2010-07-06T18:26:23.277 回答
2

在我看来,您可能会从使用事件中受益,这样您就可以将要处理的项目通知推送给听众。常规 .NET 事件按照它们订阅的顺序进行处理,因此如果需要排序,您可能会采用更明确的方法。

您可能还想查看 Reactive Framework。

于 2010-07-06T18:18:02.730 回答
2

我强烈建议不要传递枚举器本身;除了需要当前值之外,您有什么理由这样做?

除非我遗漏了一些明显的东西,否则我建议让你的实用函数简单地将你枚举的类型作为参数,然后有一个foreach处理实际枚举的外部循环。

也许您可以提供一些额外的信息来说明您迄今为止做出此设计决定的原因。

于 2010-07-06T18:13:12.783 回答
1

如果我理解正确,您有许多方法都可以在序列上调用 MoveNext,并且您希望这些方法相互协作,因此您传递了一个IEnumerator<T>. 正如您所提到的,这里肯定存在一些紧密耦合,因为您希望枚举器在每个方法的入口处处于特定状态。听起来您真正想要的是 Stream 类,它既是集合(某种意义上)又是迭代器(具有当前位置的概念)。我会将您的迭代和您需要的任何其他状态包装在您自己的类中,并将各种方法作为该类的成员

于 2010-07-06T18:23:41.437 回答