如何防止和OrderedCollection删除#do:loop中的元素?

时间:2016-09-26 17:01:01

标签: smalltalk

考虑以下书面代码:

oc do: [:elem | self doSomethingWith: elem]

众所周知,这里潜在的问题是以某种方式#doSomethingWith:伸出ocOrderedCollection)并删除其中的一些元素。

推荐的解决方案是将上面的内容写成

oc copy do: [:elem | self doSomethingWith: elem].

嗯,是的,但是我们每次枚举时都不会复制所有集合。我们呢?

实际问题是每个元素的处理都很难遵循,以至于在我们不知情的情况下最终会删除元素。在上面的例子中,如果oc的某些元素在#doSomethingWith:的上下文中被移除,我们将获得Error。不是吗?

不是真的。问题将被忽视。看看这个例子:

oc := #(1 2 4) asOrderedCollection.
oc do: [:i | i even ifTrue: [oc remove: i]]

在这种情况下,我们不会得到错误,并且元素4也不会被处理(即,在这种情况下它不会被删除)。因此,我们将默默地从枚举中跳过元素

这是为什么?好吧,因为#do:的实施方式。以Squeak为例:

do: aBlock 
  "Override the superclass for performance reasons."
  | index |
  index := firstIndex.
  [index <= lastIndex]
    whileTrue: 
      [aBlock value: (array at: index).
      index := index + 1]

请参阅?动态检查lastIndex,这就是为什么我们不会超出当前大小,并且不会发出Error信号。

我的问题是这是有目的还是有更好的解决方案。一个可行的方法是在迭代之前将lastIndex保存在临时中,但我不确定这是否是首选。

1 个答案:

答案 0 :(得分:1)

1)正如aka.nice已经指出的那样,获取并记住最初的lastIndex并不是一个好主意。这可能会使事情变得更糟,并导致更多麻烦。

2)提供的OrderedCollection并没有真正准备好,也不喜欢在迭代时修改接收器。

3)更好的解决方案是首先收集要删除的元素,然后在执行: - 处理之后,在第二步中删除它们。但是,据我所知,你不能这样做。

可能的解决方案:

a)创建OrderedCollection的子类,重新定义do: - 并重新定义removeXXX-和addXXX-方法。后者需要告诉迭代器(即do方法)关于发生了什么。 (如果删除/添加的索引在当前的do-index之前,请小心......)。 通知可以通过可执行的信号/异常来实现,该异常信号/异常在修改方法中发出信号并被捕获在do-loop代码中。

b)创建一个包装类作为Seq.Collection的子类,它将原始集合作为instvar,并将选定的消息转发给其(包装的)原始集合。 与上面类似,重新定义do:和此包装中的remove / add方法并执行相应的操作(.e。再次发出更改的信号)。

如果代码需要可重入(即如果另一个代码在包装集合上进行循环),请注意保持状态的位置;然后你必须在do方法中保持状态并使用信号来传达变化。

然后用类似的方式枚举集合:

(SaveLoopWrapper on:myCollection) do:[: ...
].

并确保执行remove的代码也看到了wrapper-instance;不是myCollection,所以添加/删除真的被抓住了。

如果你不能到后来,还有另一个黑客,我想到:使用MethodWrappers,你可以改变一个单独的实例的行为并引入钩子。 例如,创建一个OrderedCollection的子类,使用这些钩子,你可以:

 myColl changeClassTo: TheSubclassWithHooks
在迭代之前

然后(受保证保护:)撤消循环后的包装。