当类没有实现 IEnumerable 时,GetEnumerator 方法是不是仍然是幂等的

Posted

技术标签:

【中文标题】当类没有实现 IEnumerable 时,GetEnumerator 方法是不是仍然是幂等的【英文标题】:Should a GetEnumerator method still be idempotent when the class does not implement IEnumerable当类没有实现 IEnumerable 时,GetEnumerator 方法是否仍然是幂等的 【发布时间】:2011-05-10 20:13:18 【问题描述】:

这个问题是我提出的另一个question的问题,它是关于通过在迭代对象时修改对象来滥用 IEnumerable 接口的。

普遍的共识是任何实现 IEnumerable 的东西都不应该是幂等的。但是 .net 支持使用 foreach 语句进行编译时鸭子类型。任何提供 IEnumerator GetEnumerator() 方法的对象都可以在 foreach 语句中使用。

那么GetEnumerator方法应该是幂等的还是实现IEnumerable的时候呢?

编辑(添加上下文)

为了说明这一点,我的建议是,在遍历队列时,每个项目都会随着它的进行而出队。此外,在调用 GetEnumerator 之后推入队列的任何新对象仍将被迭代。

【问题讨论】:

我不会将 GetEnumerator 添加到队列本身,而是定义一个函数IEnumerable<T> DequeueOnEnumeration(),它可以像foreach(T elem in queue.DequeueOnEnumeration()) 一样使用。这样语义更加更清晰。 【参考方案1】:

我建议在集合上使用 ForEach 不应该改变它,除非集合类型的名称暗示这将会发生。我心中的问题是,如果执行一种方法将集合消耗为可枚举的东西(例如,允许“For Each Foo in MyThing.DequeueAsEnum”),应该返回什么。如果 DequeueAsEnum 返回一个 iEnumerable,那么有人可能希望摆脱“Dim myIEnumerable As IEnumerable = MyThing.DequeueAsEnum”,然后在两个不相交的 For-Each 循环中使用 MyIEnumerable。如果 DequeueAsEnum 返回一个 EnumerableOnlyOnce 类型,那么它的返回应该只枚举一次会更清楚一些。可以肯定的是,较新的 C# 和 VB.Net 方言中隐式类型的存在使得有人更有可能将函数返回分配给一个变量,但我不知道如何防止这种情况发生。

顺便说一句,在许多情况下,防止将类引用存储到变量中会有所帮助;有没有办法声明一个类,使得外部代码可以使用该类类型的表达式,但不能声明它的变量?

【讨论】:

【参考方案2】:

您似乎想要一个队列类,您可以从该队列类中将所有项目以一个不错的单列方式出列。

这个想法本身并没有错;我只是质疑您是否偏爱专门使用 GetEnumerator 来实现您所追求的目标。

为什么不简单地编写一个更明确的方法呢?例如,DequeueAll,或类似的东西。

例子:

// Just a simplistic example. Not the way I'd actually write it.
class CustomQueue<T> : Queue<T>

    public IEnumerable<T> DequeueAll()
    
        while (Count > 0)
        
            yield return Dequeue();
        
    

(请注意,上面甚至可以是一个扩展方法,如果它实际上代表了您想要的唯一功能,超出@987654324 已经提供的功能@.)

通过这种方式,您仍然可以获得我怀疑您所追求的“干净”的代码,而不会出现非幂等 GetEnumerator 的(潜在)混淆:

// Pretty clean, right?
foreach (T item in queue.DequeueAll())

    Console.WriteLine(item);

【讨论】:

我就是这么想的。实际上有人指出.net 4 引入了一个 IProducerConsumerCollection 接口和一个带有 GetConsumingEnumerable 方法的 BlockingCollection 实现。这个问题更多的是关于它是否是不好的做法,因为该类有一个 GetEnumerator 方法,因为该类实现了 IEnumerable 或两者都没有。 顺便说一句,我喜欢扩展方法的想法。【参考方案3】:

这个讨论是一个古老的讨论,据我所知,没有共同的共识。

请不要将(运行时)Duck-Typing 的概念与滥用编译器支持的foreach 来支持您想要的语义相混淆。

您似乎混淆的另一个概念是幂等性与不变性。根据您的措辞,您尝试描述第二个,这意味着提供枚举器的对象在枚举期间被修改。另一方面,幂等性意味着您的枚举器在调用两次时将产生相同的结果。

现在我们已经清楚了这一点,您需要仔细决定您的 IEnumerable 操作应该支持的语义。某些类型的枚举很难实现幂等(即涉及缓存),并且通常属于以下类别之一:

枚举随机变化 数据(即随机数生成器、传感器流) 枚举共享状态 (例如文件、数据库、流等)

另一方面,这仅考虑“源”操作。如果您使用枚举器实现过滤或转换操作,则应始终尝试使它们具有幂等性。

【讨论】:

我试图从问题中删除我的场景以保持问题开放。我在上一个问题中的建议是,当遍历阻塞队列时,每个项目都会在执行过程中出列。此外,在调用 GetEnumerator 之后推入队列的任何新对象仍将被迭代。 您能否澄清一下您认为实现 IEnumerable 是否相关?【参考方案4】:

不是 type 是幂等的 - 这甚至没有多大意义;你可能的意思是不可变的,但这并不清楚。 GetEnumerator 方法本身通常是幂等的。

虽然我会说通常是这种情况,但我可以设想一些特殊情况,在这些特殊情况下,使用非幂等 GetEnumerator 方法是有意义的。例如,您可能拥有只能读取一次的数据(因为它是从不会再次为相同请求提供服务的 Web 服务器流式传输的,或者类似的东西)。在这种情况下,GetEnumerator 必须有效地使数据源无效,以便将来的调用会引发异常。

当然,这些类型和方法应该非常仔细地记录下来,但我认为它们是合理的。

【讨论】:

我认为您可能是要回答的人,我看到您对类似帖子的回复,并认为您可能有一个不是硬性规定的意见。不确定您是否阅读了有关阻塞队列的其他问题,我觉得这与您的流示例相似。严格来说,您不应该能够查看队列的内容,而使用 foreach 是弹出所有项目的“一种方式”。当我说幂等时,我已经澄清了我的意思,谢谢。 您能否澄清一下您认为实现 IEnumerable 是否相关?

以上是关于当类没有实现 IEnumerable 时,GetEnumerator 方法是不是仍然是幂等的的主要内容,如果未能解决你的问题,请参考以下文章

当类的成员存储为没有此类方法的父类的对象时,如何访问该类的成员?

当类实现相同的接口时,Lambda表达式因LambdaConversionException而失败? [重复]

无法使用集合初始化程序初始化类型“”,因为它没有实现“System.Collections.IEnumerable”

Autofac 过滤器解析了 IEnumerable 实现

无法使用集合初始化程序实现类型,因为它没有实现'System.Collections.IEnumerable'

IEnumerable和IEnumerator接口