VBA技術解説
オブジェクト変数とは何か

ExcelマクロVBAの問題点と解決策、VBAの技術的解説
公開日:2019-12-02 最終更新日:2020-12-21

オブジェクト変数とは何か


VBAを使い始めてからある程度進むとオブジェクト変数を必ず使い始めることになります。
しかし、オブジェクト変数をどうやって使ったらよいのか、オブジェクト変数とはどういうものなのか・・・
ここの理解で苦しんでいることが多々あるようです。


VBA入門は現在137回までありますが、
オブジェクト変数については第52回.オブジェクト変数とSetステートメントででてきます。
基本構文→セルの扱い→VBA関数→オブジェクト
全体としてはこのような進み方になっていて、かなり基本的な部分、初歩的な段階ででてきます。
おそらく、この段階ではオブジェクトを入れる変数と言うのがある、こくらいの認識なのは仕方ない事です。

VBAである程度の事を自動化するだけなら、オブジェクトを入れる変数がオブジェクト変数であり、Setステートメントを使って変数に入れる。
この程度の理解でもほとんど困ることは無いでしょう。
しかし、さらに進んで、オブジェクト変数を引数で渡したり、配列に入れたりCollectionやDictionaryに入れたりするようになると、
思った通りに動作しないことも出てきて、オブジェクト変数って何なんだろう?という疑問が出てくることになります。

今回はオブジェクト変数についてより少し詳しく説明していきます。

そもそも変数とは何か

プログラムにおいて、扱うデータを入れて必要なときに利用するために用意した入れ物で、その入れ物に付けた名前が変数名になります。
入れ物と表現しましたが、変数には、文字や数値と言った値だけでなく、オブジェクトも入れられます。

オブジェクトを入れる変数を特にオブジェクト変数と呼びます。

変数の入れ物としての大きさは

数値であればデータ型ごとにバイト数が決まっています。
Integerは2バイト、LongやSingleなら4バイト、Doubleなら8バイト
それぞれ大きさが決まっているので、あらかじめ決められた大きさの変数(入れ物)を用意する事が出来ます。
これに対して、文字列型のStringは文字列の長さが決まっていないので、あらかじめ決められた大きさの変数を用意することができません同様にオブジェクト変数も、あらかじめ決められた大きさの変数を用意できません。
あらかじめ大きさが決められない変数としては、

String型
Object型
Variant型
固有オブジェクト型(オブジェクトの種類、つまりクラス)

これらは変数に入れるものの大きさが決まっていませんし、VBA実行中にも大きさが変わります。
Stringは、文字列長が様々です。
空文字列の扱い方と処理速度について(""とvbNullString)
・String型変数のメモリ配置と取得する関数 ・空文字列について ・String変数に空文字列を入れる ・セルに空文字列を入れる ・Stringが空文字列か判定 ・InputBox関数の戻り値が空文字列判定 ・空文字列の処理方法による速度比較

Object型やVariant型は、そもそも何が入るか分かりません。
固有オブジェクト型は決められたオブジェクト(クラス)であっても、その中で保有するデータ量は様々です。
簡単な例としては、Workbookオブジェクトの大きさがまちまちなのは自然に理解できることと思います。

では、これらの変数は、どのような入れ物(変数)を用意するのでしょうか。

オブジェクト変数にオブジェクトは入っていない

もし仮にオブジェクト変数に直接オブジェクトを入れるとしたら、さまざまな不都合が起きます。
例えば、Range("A1")を複数のオブジェクト変数(例えば、変数Aと変数B)に入れた場合、
それぞれの変数(変数Aと変数B)ごとにオブジェクトが存在してしまう事になります。

Dim 変数A As Range
Set 変数A = Range("A1")
Dim 変数B As Range
Set 変数B = 変数A

変数Aと変数Bにそれぞれオブジェクトが入っているとしたら、同じRange("A1")が2つ存在する?
片方の変数のValueを変更したら、もう一方の変数のValueはどうなるの?

もちろん、Range("A1")は1つしか存在しませんし、変数BのValueを変更すれば変数AのValueも変更されます。
つまり、実際のオブジェクトは1つしか存在しないという事です。
では、変数Aと変数Bには何が入っているのかという事になります。

オブジェクト変数には、オブジェクトそのものが入っているわけではありません。
オブジェクトがどこにあるかのを示す情報が入っています。

これは、Windowsのファイルのショートカットを作る事と似ています。
ショートカットは複数作れますが、ファイルそのものは同じ1つのファイルです。
Windowsのショートカットはファイルそのものではありません、ショートカットにはファイルの場所を示す情報(ファイルのフルパス)が入っているのです。

オブジェクト変数には、オブジェクトのメモリアドレスが入っています。
2つの変数に同じオブジェクトを入れた場合、それぞれの変数の中にはオブジェクトの同じメモリアドレスが入ります。

オブジェクト変数のメモリアドレス

オブジェクト変数には、オブジェクトのメモリアドレスが入っていると言いましたが、実際に確認してみましょう。
変数のメモリアドレスを確認するには、以下の関数を使います。

VarPtr関数
変数のメモリアドレスを返します。
ObjPtr関数
オブジェクトのメモリアドレスを返します。

VBA マクロ オブジェクト変数 メモリ

実際にVBAを実行して見てみましょう。

Dim 変数A As Range
Set 変数A = Range("A1")
Dim 変数B As Range
Set 変数B = 変数A
Debug.Print "変数A:VarPtr="; VarPtr(変数A)
Debug.Print "変数A:ObjPtr="; ObjPtr(変数A)
Debug.Print "変数B:VarPtr="; VarPtr(変数B)
Debug.Print "変数B:ObjPtr="; ObjPtr(変数B)

この結果は、

VBA マクロ オブジェクト変数
※Windows10+Excel2019(64bit)
※メモリアドレスは実行のたびに変わります。

VarPtr関数の戻り値は、変数そのもののメモリアドレスですので当然別々になります。
ObjPtr関数の戻り値で、オブジェクトのメモリアドレスが同じであることが確認できます。

これは自作したクラスでも同じです。
適当にクラスを作ってやってみます。
ちなみに、クラスモジュールだけ挿入すればクラスのインスタンスは作成できます。

Dim 変数A As Class1
Set 変数A = New Class1
Dim 変数B As Class1
Set 変数B = 変数A
Debug.Print "変数A:VarPtr="; VarPtr(変数A)
Debug.Print "変数A:ObjPtr="; ObjPtr(変数A)
Debug.Print "変数B:VarPtr="; VarPtr(変数B)
Debug.Print "変数B:ObjPtr="; ObjPtr(変数B)

VBA マクロ オブジェクト変数

ObjPtr関数の戻り値を見ると同じオブジェクトを指していることが分かります。
当然、それぞれにNewした場合は、別々のオブジェクトになります。

Dim 変数A As Class1
Set 変数A = New Class1
Dim 変数B As Class1
Set 変数B = New Class1
Debug.Print "変数A:VarPtr="; VarPtr(変数A)
Debug.Print "変数A:ObjPtr="; ObjPtr(変数A)
Debug.Print "変数B:VarPtr="; VarPtr(変数B)
Debug.Print "変数B:ObjPtr="; ObjPtr(変数B)

VBA マクロ オブジェクト変数

別々のオブジェクトであることが分かります。

ただし、RangeオブジェクトやWorksheetオブジェクト等のVBAにあらかじめ用意されているオブジェクトの場合は、
別々に定義したからといって、別々のオブジェクトが作成されるわけではありません。

Dim 変数A As Range
Set 変数A = Range("A1")
Dim 変数B As Range
Set 変数B = Range("A1")

この場合のObjPtrは別々の値になりますが、
Range("A1")が複数あるわけもなく、
これらはさらにその先で管理されていて、同じオブジェクトにたどり着くようになっています。
Newで作ることの出来ないクラスに関しては、このように特殊な管理がされていると考えてください。

Is演算子によるオブジェクトの比較

Is演算子は、2つのオブジェクト参照変数を比較するために使用されます。
If オブジェクト1 Is オブジェクト2 Then
Object1とobject2の両方が同じオブジェクトを参照する場合はTrueになります。
それ以外はFalseになります。
オブジェクト変数(As Object または、As 固有オブジェクト型)の初期値はNothingです。
Is演算子による比較は参照が同じかどうか判定をします、つまり、ObjPtr関数の取得値の比較と同じ意味になります。

Dim 変数A As Worksheet
Debug.Print 変数A Is Nothing '→ True
Set 変数A = Worksheets(1)
Dim 変数B As Worksheet
Set 変数B = 変数A
Dim 変数C As Worksheet
Set 変数C = Worksheets(1)
Debug.Print 変数A Is 変数B '→ True
Debug.Print 変数A Is 変数C '→ True
Set 変数C = Worksheets(2)
Debug.Print 変数A Is 変数C '→ False

Rangeブジェクトの場合は注意が必要です。
Dim 変数A As Range
Set 変数A = Range("A1")
Dim 変数B As Range
Set 変数B = 変数A
Dim 変数C As Range
Set 変数C = Range("A1")
Debug.Print 変数A Is 変数B '→ True
Debug.Print 変数A Is 変数C '→ False

Rangeが特殊であることが分かります。

TypeOf演算子

result = TypeOf objectexpression Is typename
指定された型との間で型と互換性があるかどうかを確認します。
TypeO 演算子は、objectexpressionの実行時の型がtypenameと互換性があるかどうかを調べます。
互換性は、typenameの型のカテゴリに依存します。

VBAでの型の判定は、通常はTypeName関数で行います。
TypeName関数は、引数で指定された変数に関する情報を文字列で返します。TypeName関数 TypeName(varname) varname 必ず指定します。ユーザー定義型の変数を除く、任意のバリアント型(Variant)の変数を指定します。
Implements(インターフェイス)使用時には、TypeName関数での判定とTypeOf演算子での判定で違いが出ます。
Implementsを指定したクラスの場合、
TypeName関数は、Implementsとは関係なくそれぞれのクラス名が返されますが、
TypeOf演算子は互換性を確認するので元のクラスと同一判定されます。


オブジェクト変数を使う時の注意点

ByValでもオブジェクトの中は変更される
ByRefは参照渡し、つまり、変数そのものを渡しています。
ByValは値渡し、つまり、変数の中の値を渡しています。
言い換えると、
ByRefは、VarPtrを渡しています。
ByValは、ObjPtrを渡しています。

Sub test1()
  Dim myRng As Range
  Set myRng = Range("A1")
  myRng.Value = "test"
  Call test1_sub(myRng)
  Debug.Print myRng.Value
End Sub

Sub test1_sub(ByVal arg As Range)
  arg.Value = "test_sub"
End Sub

この結果は、
test_sub
と出力されます。
ByValだからと言って、オブジェクトの中身を渡せるわけはなく、あくまでオブジェクトのアドレスを渡しているという事です。
そして、オブジェクトのアドレスを受け取れば、そのオブジェクトに対する操作はByRefと同じ結果となります。

Sub test2()
  Dim myRng As Range
  Set myRng = Range("A1")
  myRng.Value = "test"
  Call test2_sub(myRng)
  Debug.Print myRng.Address
End Sub

Sub test2_sub(ByVal arg As Range)
  Set arg = Range("A2")
End Sub

この結果は、
$A$1
と出力されます。
オブジェクト変数でのByRefとByValの違いは、
呼び出された中でオブジェクト変数に別のオブジェクトを入れたとき、呼び出し元のオブジェクト変数がいれかわるかどうかだけになります。

Withを使ったときの勘違い
Withは単なる表記の省略ではありません。
コレクションの1要素であるオブジェクトをWithで使う時は注意が必要です。

Sub test3_1()
  Debug.Print Worksheets(1).Range("A1").Address
  Rows(1).Insert
  Debug.Print Worksheets(1).Range("A1").Address
End Sub

記述が長いので、Withでまとめてみましょう。

Sub test3_2()
  With Worksheets(1).Range("A1")
    Debug.Print .Address
    Rows(1).Insert
    Debug.Print .Address
  End With
End Sub

上記2つのVBAでは結果が違ってきます。
前者のtest3_1では、
$A$1
$A$1
後者のtest3_2では、
$A$1
$A$2

それぞれに見れば、結果は当たり前に見えるかもしれません。
しかし、Withが単なる表記の省略として考えてしまうと、上記のような書き直しの間違いを起こす可能性があります。

Withで捉えたものは、その時点のRange("A1")のオブジェクトです。
行挿入によって、Withで捉えたオブジェクトはコレクションの中で位置がずらされます。
そして、Withの中では常にWithで捉えたオブジェクトを参照します。

Withにコレクション内のオブジェクトを指定する場合は、
With内では、そのオブジェクトのコレクション内での位置をずらすような記述はしないようにしましょう。
記述時点ではそれが正しい処理であったとしても、後々のトラブルの元になります。

オブジェクトが破棄されるのは
1つのオブジェクトを複数のオブジェクト変数に入れた時、
全てのオブジェクト変数が解放(= Nothing)されたときにオブジェクトは破棄されます。

Sub test3()
  Dim 変数A As Class1
  Set 変数A = New Class1
  変数A.gValue = "変数A"
  Dim 変数B As Class1
  Set 変数B = 変数A
  Set 変数A = Nothing
  Set 変数B = Nothing 'これでClass1は破棄されます。
End Sub

ただし、当り前ではありますが、
RangeオブジェクトやWorksheetオブジェクトが破棄されることはありません。

オブジェクト変数の最後に

オブジェクト変数の挙動をしっかり理解できるようになるには、相応の学習時間が必要かもしれません。
しかし、これを理解できずには自在にVBAを使いこなすことはできません。
そして、一度理解してしまえば、その挙動は至極当たり前に思えてきます。
当たり前の挙動と思えるようになるまでは、オブジェクト変数を扱う時は常にその挙動に注意してVBAを書き進めるようにしてみてください。

以下はオブジェクト関数に関する記事にになります、あわせてお読みください。
Withステートメント|VBA入門
・Withの構文 ・Withを使った時と使わない時の比較 ・Withの使用例 ・Withのネスト ・Withを使ったときに気を付けるべき書き方 ・Withの使いどころ ・サイト内の参考ページ
オブジェクト変数とSetステートメント|VBA入門
・オブジェクト変数 ・個有のオブジェクト型とは ・Setステートメント ・Setステートメントの使用例 ・WithとSetの使い分け方 ・Setステートメントの実践的な使い方 ・Is演算子によるオブジェクトの比較 ・最後に
VBAにおける変数のメモリアドレスについて
・メモリアドレスを取得する関数とメモリコピーのWindowsAPI ・メモリアドレスを確認するために使用したVBA ・文字列型Stringのメモリアドレス ・Integer, Long, Single, Double, Dateのメモリアドレス ・Variantのメモリアドレス ・配列のメモリアドレス ・オブジェクトのメモリアドレス ・ByRef,ByValのメモリアドレス



同じテーマ「マクロVBA技術解説」の記事

文字列結合&でコンパイルエラーになる理由
手動計算時の注意点と再計算方法
VBAの用語について:ステートメントとは
オブジェクト変数とは何か
VBAの小数以下の演算誤差について
スピルでVBAの何が変わったか
CharactersプロパティとCharactersオブジェクト
ユーザーに絶対に停止させたくない場合のVBA設定
印刷範囲の設定・印刷範囲のクリア
VBAの省略可能な記述について
VBAのVariant型について


新着記事NEW ・・・新着記事一覧を見る

ExcelマクロVBA入門目次|エクセルの神髄(2024-03-20)
VBA10大躓きポイント(初心者が躓きやすいポイント)|VBA技術解説(2024-03-05)
テンキーのスクリーンキーボード作成|ユーザーフォーム入門(2024-02-26)
無効な前方参照か、コンパイルされていない種類への参照です。|エクセル雑感(2024-02-17)
初級脱出10問パック|VBA練習問題(2024-01-24)
累計を求める数式あれこれ|エクセル関数応用(2024-01-22)
複数の文字列を検索して置換するSUBSTITUTE|エクセル入門(2024-01-03)
いくつかの数式の計算中にリソース不足になりました。|エクセル雑感(2023-12-28)
VBAでクリップボードへ文字列を送信・取得する3つの方法|VBA技術解説(2023-12-07)
難しい数式とは何か?|エクセル雑感(2023-12-07)


アクセスランキング ・・・ ランキング一覧を見る

1.最終行の取得(End,Rows.Count)|VBA入門
2.RangeとCellsの使い方|VBA入門
3.セルのコピー&値の貼り付け(PasteSpecial)|VBA入門
4.繰り返し処理(For Next)|VBA入門
5.変数宣言のDimとデータ型|VBA入門
6.ブックを閉じる・保存(Close,Save,SaveAs)|VBA入門
7.並べ替え(Sort)|VBA入門
8.条件分岐(IF)|VBA入門
9.セルのクリア(Clear,ClearContents)|VBA入門
10.マクロとは?VBAとは?VBAでできること|VBA入門




このサイトがお役に立ちましたら「シェア」「Bookmark」をお願いいたします。


記述には細心の注意をしたつもりですが、
間違いやご指摘がありましたら、「お問い合わせ」からお知らせいただけると幸いです。
掲載のVBAコードは動作を保証するものではなく、あくまでVBA学習のサンプルとして掲載しています。
掲載のVBAコードは自己責任でご使用ください。万一データ破損等の損害が発生しても責任は負いません。



このサイトがお役に立ちましたら「シェア」「Bookmark」をお願いいたします。
本文下部へ