2019年1月17日木曜日

[MQL4] ObjectDelete() をループ内で実行するときには decrement にする

特定の Object をまとめて消したい時、以下のようにループで各オブジェクトを消したくなります。

しかし、以下は Object が2個以上あるとうまくいきません。

```mq4
// NG
for (int i = 0; i < ObjectsTotal(EMPTY); i++) {
    string objectName = ObjectName(i);
    ObjectDelete(objectName);
}
```



ループの中で [ObjectDelete()](https://docs.mql4.com/objects/objectdelete) を実行しているため、
[ObjectsTotal()](https://docs.mql4.com/objects/objectstotal) の値が減っていってしまうためです。

では、先に `ObjectsTotal()` を変数に退避しておけばよいかというと、それもうまくいきません。
ループの後半になると、既に存在しないインデックスを参照してしまうため、[ObjectName()](https://docs.mql4.com/objects/objectname) が `""` (空文字列)を返すからです。

この手の、「イテレーティブなオブジェクトを参照しながら編集してはいけない」というのは、
プログラマが最初の方に覚えるノウハウです。
しかし、「ついやってしまう系」の代表でもあります特に言語によって少しずつ扱いが異なるので。

### 解決策
MQL4 の場合は以下のようにインデックスを decrement にするとうまくいきます。

```mq4
// OK
for (int i = ObjectsTotal(EMPTY) - 1; i >=0 ; i--) {
    string objectName = ObjectName(i);
    ObjectDelete(objectName);
}
```

### 100% 確実ではない
しかし、この方法は Objects 配列の内部実装によるところが多いです。
今のところ、これでうまく動いていますが、100% 確実とは言えません。

たまたまかもしれませんし、将来、動作が変わる可能性もあります。

私は、「もし`ObjectName()` が取れなかったらエラーが出て止まる」で良いというスタンスで上記を使用していますトレードに直接関わるようなオブジェクトの使い方をしていないので。

リアルタイムのトレードサイン等のように、絶対間違って欲しくない時は、先に ObjectName のリストを保存しておき、それを用いて `ObjectDelete()` を実行した方が良いかもしれません。

### ObjectsDelteAll() の使用も検討する
自分でループするより、
まとめてオブジェクトを消すための関数、[ObjectsDelteAll()](https://docs.mql4.com/objects/objectsdeleteall) が使えないか検討するのも良いかと思います。

オブジェクトの種類や名前のプレフィックスで選択的に削除も出来ますプレフィックスで削除できるのは実はこれを書いていて知った…