配列を宣言した時点で実は要素数が0
具体的にどんな場合か
配列が空かUBoundで確かめるケース
実はこのUBound(ar)がはまった。
次のような例を挙げる。Excel,Word, Accessどれでもいい。
abcdefgという文字列にひらがなの「あ」がマッチするか。
当然しない。
この場合、If Mc.Count > 0 Then
とするのが常識だ。しかし、なぜかこのabcdefgに文字列があることを前提にFor nextで代入するコードを書いてしまった。
MCに値が存在しないため、何も入らない
この時に空の配列変数が出来上がる。
OptionExplicitOptionBase0SubTestFunction()DimReg:SetReg=CreateObject("VBScript.RegExp")DimstrAsStringDimMC,M,iMAsLongDimar(),iarstr="abcdefg"WithReg.Pattern="あ".Global=TrueSetMC=.Execute(str)EndWithiar=0ForiM=0ToMC.Count-1ReDimPreservear(0Toiar)ar(iar)=MC.Item(iM).Valueiar=iar+1NextDebug.PrintArrayVariableisEmpty(ar)EndSubFunctionArrayVariableisEmpty(ByRefvarArray)AsBoolean'この関数はツール(T) オプション(O) [全般]タブ クラスモジュールで中断(R) または、エラートラップ エラー処理対象外のエラーで中断(E) にしないと動かない'エラー発生時に中断(B)だとUboundで処理が止まるためうまくいかない'Break On All Errors ,Break in the class module, BREAK ON UNHANDLED ERRORS(*)OnErrorGoToErr_HandleDebug.Print"IsArray ",vbTab,IsArray(varArray)Debug.Print"IsDate ",vbTab,IsDate(varArray)Debug.Print"IsEmpty ",vbTab,IsEmpty(varArray)Debug.Print"IsError ",vbTab,IsError(varArray)Debug.Print"IsMissing ",vbTab,IsMissing(varArray)Debug.Print"IsNumeric",vbTab,IsNumeric(varArray)Debug.Print"IsNull ",vbTab,IsNull(varArray)Debug.Print"IsObject ",vbTab,IsObject(varArray)If(0<UBound(varArray,1))ThenArrayVariableisEmpty=FalseElseArrayVariableisEmpty=TrueEndIfErr_Handle:IfErr.Number=9ThenArrayVariableisEmpty=TrueEndFunction
実際のエラー設定と結果
Image may be NSFW.
Clik here to view.
わざわざ実行時エラー"9"を設定しているのに、UBoundで処理が中断する。
Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.
以上のようにIf文で停止する。このため関数がうまく働かない。
もちろんエラー処理の設定を変えれば動く。しかし、VBEのエラー処理の設定次第で動かない関数は使用することができない。自分で使う分はいいが、他人に配布する場合にはこの関数は注意書きが必要。
もっともエラー発生時に中断を選ぶなとも言えるが。
http://hanatyan.sakura.ne.jp/vb6/error01.htm
[エラー発生時に中断] を使用して、ゼロによる除算で停止することは可能ですが、[エラー発生時に中断] は、ほとんどの場合あまり適切ではありません。エラーのたびに停止し、エラー処理コードが記述されているエラーでも停止するからです。
としてクラスモジュールで中断を推奨している。
上記の関数を作成するために参考にした
http://www.openreference.org/articles/view/583
これらのサイトの関数も同様にエラー処理の設定によっては途中で止まってしまう。
エラーハンドルをする場合はエラー発生時に中断はしなくてよいのである。
https://qiita.com/nukie_53/items/9a7a1eb07eff50ae1e8b
このように意識して配列の要素数が0(この記事で言う空)を求めている場合はまだあきらめがつくが、そもそも代入する要素がなくて空になる場合を想定していなかったためハマってしまった。
エラー処理の設定と相性が悪いのはUBound
ということは言える。
UBoundを使用しないで要素数を判定し、エラーの回避する案
Option Base 1の場合を想定し、ループが回ったかどうかのためのカウンタ用変数を作る。
ループが回らなければ0であり、配列は要素数にかかわらずなんら代入がなされていないことになる。
実際にOption Base1を宣言してみる。このときiarの初期値は1、Redim Preserve ar(1 to iar)
とする
OptionExplicitOptionBase1SubTestFunction2()DimReg:SetReg=CreateObject("VBScript.RegExp")DimstrAsStringDimMC,M,iMAsLongDimar(),iarDimCntstr="abcdefg"WithReg.Pattern="あ".Global=TrueSetMC=.Execute(str)EndWithiar=1Cnt=0ForiM=0ToMC.Count-1ReDimPreservear(1Toiar)ar(iar)=MC.Item(iM).Valueiar=iar+1Cnt=Cnt+1NextIfCnt>0ThenDebug.Print"Array is not Empty."ElseDebug.Print"Array is Empty."EndSub
やはりMatchCollectionを表すMCには全く要素がないため、ループカウンタが回らず、Cnt=0と判定される。
つまり動的配列をRedimで回すときは、ループカウンタ用の変数を用いて0かどうかを判定したほうが良いということになる。
エラー処理の設定によってUBoundで処理が止まることが本質
今回は関数の処理で困ってしまったのだが、今回の問題の本質はUBoundと思われる。このほかにエラー処理の設定で動かないというのを作った記憶がない。ほかに困った例があったら教えてください。
今回のまとめ
- エラーハンドルを設定してもVBEのエラー設定自体で効かなくなることがある。
- VBEのエラー処理の設定の差が明瞭に現れるのがUBoundで空(要素数0 )の配列変数の要素数を求める場合である。
- エラー処理の設定を、エラー発生時に中断にしていると、On Error Goto も効かないが、On Error Resume Nextも効かない。
- これを回避するには、UBound(ar) で要素数を判定する場合必ずarに値が入っている(要素数が0ではない)という前提で用いなければならない。
- もしくは
If Mc.Count > 0 Then
を設けてチェックする。配列に要素が入らない状況をUBound以外の方法で判定する。 - UBound自体の使用を回避する方法として上記の他、代入するときのループが回ったかどうかで判定する方法が考えられる。この場合はエラー処理の設定に注意しなくて良い。Option Baseにも影響されない。
- Uboundを用いるときは0要素にしないか0要素を判定する方法を設けること。エラー処理の設定、Option Baseにも注意を払うこと。
- エラー処理の設定は「クラスモジュールで切断」が一番有効らしい。