在接口中使用 IEnumerator<T> 的模式
-
02-10-2019 - |
题
我有一个 C# 类,需要处理一系列项目(IEnumerable<T>
)跨越了一堆方法,所以我不能简单地 foreach
在一个方法内部。我打电话 .GetEnumerator()
并通过这个 IEnumerator<T>
它非常有效,为我在循环单个序列时提供了所需的灵活性。
现在我想允许其他人在这个过程中添加逻辑。最自然的方法是为它们提供一个接口,其中的方法接受 IEnumerator<T>
. 。简单,完成,并且有效。
但我担心这是一种反模式。他们必须知道 IEnumerator<T>
已经有了 .MoveNext()
被调用,这样他们就可以简单地访问 .Current
. 。另外我没有看到任何使用的先例 IEnumerator<T>
在要实现的接口中。
- 我没有考虑哪些陷阱?
- 是否有另一种模式可以让我这样做 高效的 机制(即我不想在不暴露的情况下创建/销毁多个副本)
IEnumerator<T>
本身?
更新: 正如我在下面的评论中提到的:我想要的是某种通用的 Stream<T>
. 。我需要能够有效地看到下一个项目(IEnumerator.Current
-> .Peek()
)并消费它(IEnumerator<T>.MoveNext()
-> .Pop()
).
我用了 IEnumerator<T>
因为它符合接口要求。我更喜欢在合适的情况下使用常见的 BCL 类型,但我似乎滥用了这一类型。
那么问题3)有没有一个类可以满足这个需求?或者我应该创建自己的 Stream 来延迟执行 IEnumerator<T>
内部?然后它将被完全封装。我不想使用许多现有的集合,因为它们有内部存储,而我希望存储是 IEnumerable<T>
本身。
好吧,听起来大家的共识是 IEnumerator<T>
经常是一个 ValueType
以及不知道先验的状态 IEnumerator<T>
, ,传递它通常是一个坏主意。
我听到的最好的建议是创建我自己的类并进行传递。还有其他建议吗?
解决方案
如果我理解正确的话,您有许多方法都可以在序列上调用 MoveNext,并且您希望这些方法彼此协作,因此您传递一个 IEnumerator<T>
. 。正如您所提到的,这里肯定存在一些紧密耦合,因为您希望枚举器在每个方法的入口处处于特定状态。听起来你真正想要的是类似 Stream 类的东西,它既是一个集合(某种程度上)又是一个迭代器(具有当前位置的概念)。我会将您的迭代和您需要的任何其他状态包装在您自己的类中,并将各种方法作为该类的成员
其他提示
你应该 确实 不通过 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())
尝试预测每种情况的结果:)
诚然,这是一个特别邪恶的例子,但它确实展示了一些与传递可变状态相关的极端情况。我强烈鼓励您在“中心”方法中执行所有迭代,该方法仅使用当前值调用适当的其他方法。
我强烈建议不要传递枚举器本身;除了需要当前值之外,您还有什么理由这样做?
除非我遗漏了一些明显的东西,否则我建议您的实用程序函数只需采用您枚举的类型作为参数,然后有一个外部 foreach
处理实际枚举的循环。
也许您可以提供一些额外的信息来说明您迄今为止为何做出此设计决策。
在我看来,您可能会受益于使用事件,以便您可以将要处理的项目的通知推送给侦听器。常规 .NET 事件按照订阅的顺序进行处理,因此如果需要排序,您可能会采用更明确的方法。
您可能还想查看响应式框架。