【问题标题】:Side effects in an iterator considered harmful?迭代器中的副作用被认为是有害的?
【发布时间】:2008-11-26 11:34:30
【问题描述】:

我今天编写了我的第一个 C# 迭代器。哇哦。

有趣的是,它有副作用。我的迭代器从目录中过滤掉无效文件并返回一系列有效文件进行处理。当遇到无效文件时,它会将其移动到另一个目录。

我尝试将其实现为 LINQ 查询,但真的不喜欢 where 子句的谓词具有副作用的事实。那是一种明确的气味。

我可以显式地实现它,循环遍历所有文件并依次处理好或坏,但这不是很优雅。更好的解决方案是将其分成两个列表(好的和坏的)并依次处理。

但后来我想起了迭代器。我现在有了一个迭代器,它产生有效文件并处理(移动)无效文件。

所以,我的问题是:迭代器有这样的副作用是个坏主意吗?我是否在迭代器中隐藏了太多功能?

【问题讨论】:

  • @sharptooth - 你是想要分类徽章还是什么?

标签: c# oop iterator


【解决方案1】:

有副作用的迭代器不好? :)

如果您有包含所有文件的序列,则可以使用visitor-ish 访问所有项目并为每种情况调用一个函数。访问者中的歧视可以是您可以提供的谓词,也可以是访问者固有的。

所以,我不会说 C#,而是像这样的伪代码:

good_handler = new FileHandler() {
  handle(File f) { print "Yay!"; }
}

bad_handler = new FileHandler() {
  handle(File f) { print "Nay!"; }
}

files = YourFileSequence();
visitor = new Visitor(good_handler, bad_handler);
visitor.visit(files);

【讨论】:

    【解决方案2】:

    我会说通常在迭代器中产生副作用是个坏主意,但这并不是完全的禁忌。如果您有副作用,则调用者很难/不可能以纯粹的功能方式工作。这可能是也可能不是问题,具体取决于您的用例。

    我建议您有两种获取迭代器的方法 - 一种具有副作用(基本上可能是一种优化),另一种没有(更慢,但更容易推理)。这可能只是通过将标志传递到方法中,或者使用两个不同名称的方法。

    【讨论】:

      【解决方案3】:

      在逻辑上对集合进行枚举的迭代器不应该有副作用,不。特别是,当使用 IEnumerator.Reset() 方法重新启动时,它们不会是幂等的。

      然而,迭代器实际上是一种协程,它们可以用于实现一些难以以其他方式实现的东西,例如steps in an asynchronous workflow.

      【讨论】:

      • IEnumerator.Reset() 很少被实现,请注意 - 并且从未在迭代器块中实现。
      • 当然 - 但它是 API 的一部分这一事实表明,枚举是稳定的并且重新启动是幂等的,这是一个逻辑预期。
      • 它是 API 的一部分的事实基本上是一个错误 :) 但是是的,我认为至少 大多数 迭代器是幂等和可重复的是合理的。当然,情况并非总是如此 - 例如。从 Web 服务获取结果的 LINQ 查询每次可能会给出不同的结果。
      • (续)但人们希望给出不同结果的原因是“自然”而不是“因为你上次打电话给我”。
      【解决方案4】:

      另一个问题是该方法可能被“误用”,因为调用者可能会尝试使用它来移动文件,而不会对返回的结果真正感兴趣。

      如果调用者从不迭代结果,则(预期的)副作用不会由于迭代器的延迟执行而被调用。甚至可能存在用户仅迭代集合的一部分的情况,因此对某些项目而不是全部执行副作用。

      这个问题在这篇文章中讨论:http://codequota.com/archive/2012/02/13/iterator-blocks-and-side-effects.aspx

      【讨论】:

        【解决方案5】:

        我的经验法则是,如果我正在迭代一个集合,则不会。但在 Python 中,for 循环通常习惯性地用于执行代码一定次数,在这种情况下,我使用它并没有任何副作用。

        【讨论】:

          【解决方案6】:

          谢谢大家 - 天哪!快速响应!

          我不得不同意迭代器中的副作用是一个坏主意。我不得不问的事实表明有气味。应该听听我的蜘蛛侠的感觉。

          我认为我问的主要原因是因为我的副作用与主要任务完全隔离,因此巧妙地封装在迭代器中。但是,它仍然是隐藏的功能,这不是很好。

          另外,我认为我将访问者的想法与迭代器混为一谈,这也不是一个好主意。

          我已经改变了我的实现,从所有文件的原始序列中生成了 2 个序列 - 一个好,一个坏。我现在可以以更明显和直观的方式处理它们。万岁。

          所以,我还没有在现实世界中使用过迭代器。哦,好吧。

          谢谢! 马特

          【讨论】:

            【解决方案7】:

            我会说副作用是一个坏主意,但无害。如果你有副作用,你基本上是在做两个操作。最好把这些操作分成两个函数,这样代码更容易维护,你可以分开做。

            在这种情况下,您将坏文件从文件夹中移出,并将其他内容移到好文件中。分离这些操作可以让您移动坏文件而不选择好文件,或者让您对好文件进行操作(例如计数)而不移动坏文件。您的代码也将更加分隔,因此在您需要时可以更轻松地优化其中一项操作。

            【讨论】:

              【解决方案8】:

              我认为实际上存在比迭代器隐藏的副作用更直接的问题。也就是说:您正在更改它正在迭代的集合的成员资格。即使副作用没有难闻的代码气味,这也是您必须谨慎对待的事情。如果您要从集合中删除内容,那么您可以通过一些方法来实现这一点,这些方法看起来很明智(例如缓存文件列表并在重置迭代器时重用它)。

              【讨论】:

                【解决方案9】:

                我不会对一般情况发表评论,但在你的情况下,我认为这是危险。衡量界面质量的一个很好的指标是正确使用界面的难易程度和错误使用的难易程度。

                应用该指标,您的设计得分非常低,因为它难以置信很容易错误地使用它:只需对其进行两次迭代。

                我实际上会比 Jon 更进一步说:甚至不提供选项。这可能会有所帮助,但可能使用此错误的代价可能太高了。另一方面,可以说,如果用户故意做出选择,他必须应对后果。

                【讨论】:

                  猜你喜欢
                  • 2015-09-09
                  • 2011-12-28
                  • 1970-01-01
                  • 2011-08-12
                  • 2014-11-24
                  • 2010-12-22
                  • 2010-11-08
                  • 1970-01-01
                  • 2014-11-28
                  相关资源
                  最近更新 更多