For Eachステートメントで使用する制御変数には、指定したコレクションのメンバと同じオブジェクト型変数か、バリアント型変数を指定しなければなりません。
変数のオブジェクト型には、固有オブジェクト型と総称オブジェクト型の2種類があります。固有オブジェクト型は、変数の型宣言で、固有のオブジェクト型を指定します。たとえば、次のコードでは、Range型の変数と、Worksheet型の変数を宣言しています。
Sub Sample() Dim buf As Range Dim tmp As Worksheet End Sub
Range型として宣言した変数bufには、Rangeオブジェクトしか格納することができず、Worksheetオブジェクトを格納しようとするとエラーになります。本当は"格納"ではなく"参照"なのですが、ここでは便宜的に"格納する"という表現で解説します。
一方の総称オブジェクト型は、すべてのオブジェクトを格納できる型です。変数を宣言するときはObjectを指定します。
Sub Sample() Dim buf As Object Dim tmp As Object End Sub
総称オブジェクトのObjectで宣言した変数には、RangeオブジェクトでもWorksheetオブジェクトでも、すべてのオブジェクトを格納できます。
For Eachステートメントで使用する制御変数には、Inの後で指定するコレクションのメンバと同じオブジェクト型を指定しなければなりません。
Sub Sample() Dim C As Range For Each C In Range("A1:B3") MsgBox C.Value Next C End Sub
Sub Sample() Dim ws As Worksheet For Each ws In Worksheets MsgBox ws.Name Next ws End Sub
または、固有オブジェクト型ではなく、総称オブジェクト型を使用することもできます。
Sub Sample() Dim C As Objext For Each C In Range("A1:B3") MsgBox C.Value Next C End Sub
Sub Sample() Dim ws As Object For Each ws In Worksheets MsgBox ws.Name Next ws End Sub
あるいは、オブジェクト型に限らず、すべての型を格納できるバリアント型(Variant)を使用することも可能です。
Sub Sample() Dim C As Variant For Each C In Range("A1:B3") MsgBox C.Value Next C End Sub
Sub Sample() Dim ws As Variant For Each ws In Worksheets MsgBox ws.Name Next ws End Sub
つまり、For Eachステートメントの制御変数には、次のいずれかの型を指定することになります。
バリアント型の意味が正確には少し違いますが、おおむね次のようなイメージです。
このうち、本エラーが発生するのは、For Eachステートメントの制御変数に、固有オブジェクト型・総称オブジェクト型・バリアント型 以外の型を指定したときです。
固有オブジェクト型を指定しているのですが、そのオブジェクト型が、コレクションのメンバと一致しないときは、別のエラーが発生します。
次のコードで、コレクションのメンバはRangeオブジェクトですが、制御変数にWorksheet型を指定しています。
Sub Sample()
Dim C As Worksheet
For Each C In Range("A1:B3")
MsgBox C
Next C
End Sub
オブジェクト型変数を使用するときは、総称オブジェクト型ではなく固有オブジェクト型を使う方がいいと言われています。その理由のひとつは「総称オブジェクト型より固有オブジェクト型の方が、マクロの実行速度が速い」です。For Eachステートメントの制御変数の場合も、総称オブジェクト型と固有オブジェクト型で速度の違いがあるのでしょうか。実際に試してみました。
Declare Function GetTickCount Lib "kernel32" () As Long Sub Sample1() Dim Start As Long Dim C As Range Range("A1:X256").Clear Start = GetTickCount For Each C In Range("A1:X256") C.Value = Int(Rnd * 6) + 1 C.Interior.ColorIndex = C.Value Next C Debug.Print (GetTickCount - Start) / 1000 End Sub
For Eachステートメントで大量のセルを操作しました。制御変数Cの型を「固有オブジェクト型(Range)」「総称オブジェクト型(Object)」「バリアント型(Variant)」に変えて、それぞれ5回実行します。Range("A1:X256")は、約6000個のセルです。まったく同じコードで、セル数が約12000個のRange("A1:X512")でも試してみました。計測は、2台のパソコンで行いました。1台はAspire Oneという、最近流行のネットブックです(これをPC-Aとします)。もう1台のパソコンは、事務所の中でも比較的スペックの低いデスクトップです(これをPC-Bとします)。
【PC-Aで約6000個のセル操作】
Range | Object | Variant | |
---|---|---|---|
1回目 | 3.297 | 3.281 | 3.406 |
2回目 | 3.266 | 3.406 | 3.375 |
3回目 | 3.234 | 3.406 | 3.328 |
4回目 | 3.235 | 3.312 | 3.328 |
5回目 | 3.218 | 3.359 | 3.344 |
平均 | 3.250 | 3.353 | 3.356 |
割合(%) | 100 | 103.163 | 103.268 |
【PC-Aで約12000個のセル操作】
Range | Object | Variant | |
---|---|---|---|
1回目 | 6.407 | 6.438 | 6.359 |
2回目 | 6.250 | 6.453 | 6.563 |
3回目 | 6.344 | 6.359 | 6.438 |
4回目 | 6.328 | 6.359 | 6.422 |
5回目 | 6.437 | 6.468 | 6.453 |
平均 | 6.353 | 6.415 | 6.447 |
割合(%) | 100 | 100.979 | 101.476 |
【PC-Bで約6000個のセル操作】
Range | Object | Variant | |
---|---|---|---|
1回目 | 7.719 | 6.344 | 6.328 |
2回目 | 7.531 | 7.704 | 7.734 |
3回目 | 8.172 | 6.187 | 6.235 |
4回目 | 8.063 | 7.875 | 6.141 |
5回目 | 6.141 | 7.875 | 7.688 |
平均 | 7.525 | 7.197 | 6.825 |
割合(%) | 100 | 95.639 | 90.698 |
【PC-Bで約12000個のセル操作】
Range | Object | Variant | |
---|---|---|---|
1回目 | 10.157 | 11.141 | 10.563 |
2回目 | 11.984 | 10.500 | 10.484 |
3回目 | 10.125 | 10.500 | 12.016 |
4回目 | 12.157 | 12.125 | 10.516 |
5回目 | 11.703 | 10.485 | 10.907 |
平均 | 11.225 | 10.950 | 10.897 |
割合(%) | 100 | 97.550 | 97.078 |
PC-Aでは、固有オブジェクト型がほんの少しだけ速いですが、ほとんど変わりません。また、PC-Bでは逆に、固有オブジェクト型の方が遅い結果も出ています。これは、PC-Bに何か問題があるというよりも、速度差は誤差の範疇と考えてもいいでしょう。最近のパソコンでは「総称オブジェクト型より固有オブジェクト型の方が、マクロの実行速度が速い」というのは、意識するほどのことはない違いでしかありません。
固有オブジェクト型を使うメリットはほかにもあります。それは、コード記述時のインテリセンス機能です。
たとえば、制御変数をRange型で宣言した場合、その変数Cの後ろにピリオドを打つと、Range型で使用できるプロパティなどがリスト表示されます。
対して、総称オブジェクト型やバリアント型で宣言した変数では、このインテリセンス機能が働きません。
インテリセンス機能を活用したいのでしたら、固有オブジェクト型で宣言しましょう。
For Eachステートメントでは、Inの後ろに配列を指定することもできます。このときも、制御変数にはバリアント型を指定しなければなりません。
上図では、Split関数の返り値を格納した変数tmpが配列になります。For Eachステートメントで、配列tmp内の要素をひとつずつ操作しているのですが、制御変数の型を文字列型(String)としたためにエラーが発生しました。配列の要素は文字列なのですから、制御変数の型も文字列型でよさそうなものですが、For Eachステートメントで使用する制御変数は「オブジェクト型かバリアント型」と決まっています。ここではオブジェクト型を指定できませんから、バリアント型にしなければなりません。