考虑以下书面代码:
oc do: [:elem | self doSomethingWith: elem]
众所周知,这里潜在的问题是以某种方式#doSomethingWith:
伸出oc
(OrderedCollection
)并删除其中的一些元素。
推荐的解决方案是将上面的内容写成
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
保存在临时中,但我不确定这是否是首选。
答案 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
在迭代之前然后(受保证保护:)撤消循环后的包装。