5

我今天写了我的第一个 C# 迭代器。呜呼。

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

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

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

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

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

4

9 回答 9

4

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

如果你有包含所有文件的序列,你可以有一些访问者-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);
于 2008-11-26T11:44:58.880 回答
3

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

我建议您有两种获取迭代器的方法-一种具有副作用(基本上可能是一种优化),另一种没有(速度较慢,但​​推理起来更简单)。这可能只是通过将标志传递给方法,或者有两个不同命名的方法。

于 2008-11-26T11:39:31.107 回答
1

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

然而,迭代器实际上是一种协程,它们可以用于实现一些以其他方式难以实现的东西,例如异步工作流中的步骤

于 2008-11-26T11:44:58.320 回答
1

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

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

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

于 2012-02-14T02:36:52.150 回答
0

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

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

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

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

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

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

谢谢!马特

于 2008-11-26T13:56:13.110 回答
0

我会说副作用是一个坏主意,但无害。如果你有副作用,你基本上是在做两个操作。最好将这些操作分成两个函数,这样代码更易于维护,您可以单独执行它们。

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

于 2008-11-26T14:01:22.897 回答
0

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

于 2008-11-26T18:53:17.190 回答
0

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

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

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

于 2008-11-26T18:58:38.210 回答
0

我的经验法则是,如果我正在迭代一个集合,不。但是在 Python 中,通常习惯性地使用 for 循环来执行代码一定次数,在这种情况下,我可以毫无问题地使用它并产生副作用。

于 2008-11-26T13:46:13.333 回答