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

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

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


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


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

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

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

そもそも変数とは何か

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

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

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

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

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

これらは変数に入れるものの大きさが決まっていませんし、VBA実行中にも大きさが変わります。
Stringは、文字列長が様々です。
空文字列の扱い方と処理速度について(""とvbNullString)
空文字列と書きましたが、空文字列という表現がかなり曖昧な表現になっています。ここでいう空文字列とは、文字列が入るべき場所に、何も入っていない(ように見える)状態を指しています。VBAにおいては、空文字列の状態が2つあります。

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で作ることの出来ないクラスに関しては、このように特殊な管理がされていると考えてください。

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

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に指定したオブジェクトに対してオブジェクト名を再度記述することなく、プロパティやメソッドを記述することができます。文章で例えて言えば、主語を一度書いたら、その後は主語を省略するような書き方になります。
オブジェクト変数とSetステートメント|VBA入門
変数のデータ型の説明において、Object…オブジェクト型 というのがあった事を覚えているでしょうか。数値や文字ではなく、オブジェクトを入れる変数がオブジェクト変数です。オブジェクトと言っても、いろいろなものがあります。
VBAにおける変数のメモリアドレスについて
VBA開発においてメモリアドレスを気にすることはほとんど無いと思います。気になる場合があるとしたら、・String変数の処理が遅い ・Variant変数の処理が遅い ・ByRef,ByValの違い ・WindowsAPI使用時 このような場合に多少は気になる事があるくらいではないでしょうか。



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

クリップボードを使わないセルのCopy
Rangeの使い方:最終行まで選択を例に
フルパスをディレクトリ、ファイル名、拡張子に分ける
Colorプロパティの設定値一覧
VBAを定型文で覚えよう
VBAこれだけは覚えておきたい必須基本例文10
エクセルVBAでのシート指定方法
文字列結合&でコンパイルエラーになる理由
手動計算時の注意点と再計算方法
VBAの用語について:ステートメントとは
オブジェクト変数とは何か


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

オブジェクト変数とは何か|VBA技術解説(12月2日)
SQL関数と演算子|SQL入門(12月1日)
データの取得:集約集計、並べ替え(DISTINKT,GROUP BY,ORDER BY)|SQL入門(11月30日)
データの取得:条件指定(SELECT,WHERE)|SQL入門(11月29日)
データの挿入:バルクインサート|SQL入門(11月28日)
データの挿入(INSERT)と全削除|SQL入門(11月26日)
テーブル名変更と列追加(ALTER TABLE)とテーブル自動作成|SQL入門(11月25日)
テーブルの作成/削除(CREATE TABLE,DROP TABLE)|SQL入門(11月24日)
データベースに接続/切断|SQL入門(11月23日)
SQLiteのインストール|SQL入門(11月22日)


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

1.最終行の取得(End,Rows.Count)|VBA入門
2.セルのコピー&値の貼り付け(PasteSpecial)|VBA入門
3.RangeとCellsの使い方|VBA入門
4.Range以外の指定方法(Cells,Rows,Columns)|VBA入門
5.変数宣言のDimとデータ型|VBA入門
6.繰り返し処理(For Next)|VBA入門
7.マクロって何?VBAって何?|VBA入門
8.セルに文字を入れるとは(Range,Value)|VBA入門
9.空白セルを正しく判定する方法(IsEmpty,IsError,HasFormula)|VBA技術解説
10.ひらがな⇔カタカナの変換|エクセル基本操作



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

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


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



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